{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "881462bc-b115-4b49-b963-e9e68f06be41",
   "metadata": {},
   "source": [
    "# Babyweight Estimation with Transformed Data"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9992a281-8ed0-4003-aa0e-226db8434799",
   "metadata": {},
   "source": [
    "## 1. Preparation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24126cd5-8efa-4cef-9b51-617e341ea211",
   "metadata": {},
   "source": [
    "### Install required packages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "fcbf1652-be4e-4695-87f6-bf5262921c0f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: tensorflow-transform==1.6.0 in /opt/conda/lib/python3.7/site-packages (1.6.0)\n",
      "Requirement already satisfied: tfx-bsl<1.7.0,>=1.6.0 in /opt/conda/lib/python3.7/site-packages (from tensorflow-transform==1.6.0) (1.6.0)\n",
      "Requirement already satisfied: pydot<2,>=1.2 in /opt/conda/lib/python3.7/site-packages (from tensorflow-transform==1.6.0) (1.4.2)\n",
      "Requirement already satisfied: tensorflow-metadata<1.7.0,>=1.6.0 in /opt/conda/lib/python3.7/site-packages (from tensorflow-transform==1.6.0) (1.6.0)\n",
      "Requirement already satisfied: numpy<2,>=1.16 in /opt/conda/lib/python3.7/site-packages (from tensorflow-transform==1.6.0) (1.21.6)\n",
      "Requirement already satisfied: protobuf<4,>=3.13 in /opt/conda/lib/python3.7/site-packages (from tensorflow-transform==1.6.0) (3.19.6)\n",
      "Requirement already satisfied: absl-py<2.0.0,>=0.9 in /opt/conda/lib/python3.7/site-packages (from tensorflow-transform==1.6.0) (1.4.0)\n",
      "Requirement already satisfied: tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5 in /opt/conda/lib/python3.7/site-packages (from tensorflow-transform==1.6.0) (2.7.4)\n",
      "Requirement already satisfied: apache-beam[gcp]<3,>=2.35 in /opt/conda/lib/python3.7/site-packages (from tensorflow-transform==1.6.0) (2.46.0)\n",
      "Requirement already satisfied: pyarrow<6,>=1 in /opt/conda/lib/python3.7/site-packages (from tensorflow-transform==1.6.0) (5.0.0)\n",
      "Requirement already satisfied: regex>=2020.6.8 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2022.10.31)\n",
      "Requirement already satisfied: typing-extensions>=3.7.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (4.5.0)\n",
      "Requirement already satisfied: grpcio!=1.48.0,<2,>=1.33.1 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.53.0)\n",
      "Requirement already satisfied: fastavro<2,>=0.23.6 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.7.3)\n",
      "Requirement already satisfied: pytz>=2018.3 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2023.3)\n",
      "Requirement already satisfied: cloudpickle~=2.2.1 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2.2.1)\n",
      "Requirement already satisfied: objsize<0.7.0,>=0.6.1 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.6.1)\n",
      "Requirement already satisfied: pymongo<4.0.0,>=3.8.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (3.13.0)\n",
      "Requirement already satisfied: orjson<4.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (3.8.10)\n",
      "Requirement already satisfied: httplib2<0.22.0,>=0.8 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.21.0)\n",
      "Requirement already satisfied: dill<0.3.2,>=0.3.1.1 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.3.1.1)\n",
      "Requirement already satisfied: crcmod<2.0,>=1.7 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.7)\n",
      "Requirement already satisfied: requests<3.0.0,>=2.24.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2.28.2)\n",
      "Requirement already satisfied: fasteners<1.0,>=0.3 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.18)\n",
      "Requirement already satisfied: zstandard<1,>=0.18.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.18.0)\n",
      "Requirement already satisfied: proto-plus<2,>=1.7.1 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.22.2)\n",
      "Requirement already satisfied: hdfs<3.0.0,>=2.1.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2.7.0)\n",
      "Requirement already satisfied: python-dateutil<3,>=2.8.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2.8.2)\n",
      "Requirement already satisfied: google-auth-httplib2<0.2.0,>=0.1.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.1.0)\n",
      "Requirement already satisfied: google-cloud-language<2,>=1.3.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.3.2)\n",
      "Requirement already satisfied: google-cloud-bigtable<2,>=0.31.1 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.7.3)\n",
      "Requirement already satisfied: google-cloud-core<3,>=0.28.1 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2.3.2)\n",
      "Requirement already satisfied: google-cloud-bigquery<4,>=1.6.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (3.9.0)\n",
      "Requirement already satisfied: google-cloud-videointelligence<2,>=1.8.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.16.3)\n",
      "Requirement already satisfied: google-cloud-datastore<2,>=1.8.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.15.5)\n",
      "Requirement already satisfied: google-cloud-spanner<4,>=3.0.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (3.30.0)\n",
      "Requirement already satisfied: google-cloud-bigquery-storage<2.17,>=2.6.3 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2.16.2)\n",
      "Requirement already satisfied: google-cloud-dlp<4,>=3.0.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (3.12.1)\n",
      "Requirement already satisfied: google-cloud-recommendations-ai<0.8.0,>=0.1.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.7.1)\n",
      "Requirement already satisfied: google-cloud-pubsublite<2,>=1.2.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.7.0)\n",
      "Requirement already satisfied: google-apitools<0.5.32,>=0.5.31 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.5.31)\n",
      "Requirement already satisfied: google-cloud-pubsub<3,>=2.1.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2.16.0)\n",
      "Requirement already satisfied: cachetools<5,>=3.1.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (4.2.4)\n",
      "Requirement already satisfied: google-cloud-vision<4,>=2 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (3.4.1)\n",
      "Requirement already satisfied: google-auth<3,>=1.18.0 in /opt/conda/lib/python3.7/site-packages (from apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2.17.2)\n",
      "Requirement already satisfied: pyparsing>=2.1.4 in /opt/conda/lib/python3.7/site-packages (from pydot<2,>=1.2->tensorflow-transform==1.6.0) (3.0.9)\n",
      "Requirement already satisfied: flatbuffers<3.0,>=1.12 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (2.0.7)\n",
      "Requirement already satisfied: tensorflow-io-gcs-filesystem>=0.21.0 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (0.25.0)\n",
      "Requirement already satisfied: h5py>=2.9.0 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (3.8.0)\n",
      "Requirement already satisfied: google-pasta>=0.1.1 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (0.2.0)\n",
      "Requirement already satisfied: six>=1.12.0 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (1.16.0)\n",
      "Requirement already satisfied: astunparse>=1.6.0 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (1.6.3)\n",
      "Requirement already satisfied: keras<2.8,>=2.7.0rc0 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (2.7.0)\n",
      "Requirement already satisfied: gast<0.5.0,>=0.2.1 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (0.4.0)\n",
      "Requirement already satisfied: tensorflow-estimator<2.8,~=2.7.0rc0 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (2.7.0)\n",
      "Requirement already satisfied: tensorboard~=2.6 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (2.8.0)\n",
      "Requirement already satisfied: keras-preprocessing>=1.1.1 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (1.1.2)\n",
      "Requirement already satisfied: wrapt>=1.11.0 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (1.15.0)\n",
      "Requirement already satisfied: termcolor>=1.1.0 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (2.2.0)\n",
      "Requirement already satisfied: opt-einsum>=2.3.2 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (3.3.0)\n",
      "Requirement already satisfied: libclang>=9.0.1 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (16.0.0)\n",
      "Requirement already satisfied: wheel<1.0,>=0.32.0 in /opt/conda/lib/python3.7/site-packages (from tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (0.40.0)\n",
      "Requirement already satisfied: googleapis-common-protos<2,>=1.52.0 in /opt/conda/lib/python3.7/site-packages (from tensorflow-metadata<1.7.0,>=1.6.0->tensorflow-transform==1.6.0) (1.59.0)\n",
      "Requirement already satisfied: tensorflow-serving-api!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<3,>=1.15 in /opt/conda/lib/python3.7/site-packages (from tfx-bsl<1.7.0,>=1.6.0->tensorflow-transform==1.6.0) (2.7.4)\n",
      "Requirement already satisfied: pandas<2,>=1.0 in /opt/conda/lib/python3.7/site-packages (from tfx-bsl<1.7.0,>=1.6.0->tensorflow-transform==1.6.0) (1.3.5)\n",
      "Requirement already satisfied: google-api-python-client<2,>=1.7.11 in /opt/conda/lib/python3.7/site-packages (from tfx-bsl<1.7.0,>=1.6.0->tensorflow-transform==1.6.0) (1.8.0)\n",
      "Requirement already satisfied: google-api-core<2dev,>=1.13.0 in /opt/conda/lib/python3.7/site-packages (from google-api-python-client<2,>=1.7.11->tfx-bsl<1.7.0,>=1.6.0->tensorflow-transform==1.6.0) (1.34.0)\n",
      "Requirement already satisfied: uritemplate<4dev,>=3.0.0 in /opt/conda/lib/python3.7/site-packages (from google-api-python-client<2,>=1.7.11->tfx-bsl<1.7.0,>=1.6.0->tensorflow-transform==1.6.0) (3.0.1)\n",
      "Requirement already satisfied: oauth2client>=1.4.12 in /opt/conda/lib/python3.7/site-packages (from google-apitools<0.5.32,>=0.5.31->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (4.1.3)\n",
      "Requirement already satisfied: pyasn1-modules>=0.2.1 in /opt/conda/lib/python3.7/site-packages (from google-auth<3,>=1.18.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.2.8)\n",
      "Requirement already satisfied: rsa<5,>=3.1.4 in /opt/conda/lib/python3.7/site-packages (from google-auth<3,>=1.18.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (4.9)\n",
      "Requirement already satisfied: packaging>=20.0.0 in /opt/conda/lib/python3.7/site-packages (from google-cloud-bigquery<4,>=1.6.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (23.0)\n",
      "Requirement already satisfied: google-resumable-media<3.0dev,>=0.6.0 in /opt/conda/lib/python3.7/site-packages (from google-cloud-bigquery<4,>=1.6.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2.4.1)\n",
      "Requirement already satisfied: grpc-google-iam-v1<0.13dev,>=0.12.3 in /opt/conda/lib/python3.7/site-packages (from google-cloud-bigtable<2,>=0.31.1->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.12.6)\n",
      "Requirement already satisfied: grpcio-status>=1.33.2 in /opt/conda/lib/python3.7/site-packages (from google-cloud-pubsub<3,>=2.1.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.48.2)\n",
      "Requirement already satisfied: overrides<7.0.0,>=6.0.1 in /opt/conda/lib/python3.7/site-packages (from google-cloud-pubsublite<2,>=1.2.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (6.5.0)\n",
      "Requirement already satisfied: sqlparse>=0.3.0 in /opt/conda/lib/python3.7/site-packages (from google-cloud-spanner<4,>=3.0.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.4.3)\n",
      "Requirement already satisfied: docopt in /opt/conda/lib/python3.7/site-packages (from hdfs<3.0.0,>=2.1.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.6.2)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.7/site-packages (from requests<3.0.0,>=2.24.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (3.4)\n",
      "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /opt/conda/lib/python3.7/site-packages (from requests<3.0.0,>=2.24.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.26.15)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.7/site-packages (from requests<3.0.0,>=2.24.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2022.12.7)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/lib/python3.7/site-packages (from requests<3.0.0,>=2.24.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (2.1.1)\n",
      "Requirement already satisfied: tensorboard-data-server<0.7.0,>=0.6.0 in /opt/conda/lib/python3.7/site-packages (from tensorboard~=2.6->tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (0.6.1)\n",
      "Requirement already satisfied: markdown>=2.6.8 in /opt/conda/lib/python3.7/site-packages (from tensorboard~=2.6->tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (3.4.3)\n",
      "Requirement already satisfied: werkzeug>=0.11.15 in /opt/conda/lib/python3.7/site-packages (from tensorboard~=2.6->tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (2.1.2)\n",
      "Requirement already satisfied: setuptools>=41.0.0 in /opt/conda/lib/python3.7/site-packages (from tensorboard~=2.6->tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (67.6.1)\n",
      "Requirement already satisfied: google-auth-oauthlib<0.5,>=0.4.1 in /opt/conda/lib/python3.7/site-packages (from tensorboard~=2.6->tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (0.4.6)\n",
      "Requirement already satisfied: tensorboard-plugin-wit>=1.6.0 in /opt/conda/lib/python3.7/site-packages (from tensorboard~=2.6->tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (1.8.1)\n",
      "Requirement already satisfied: requests-oauthlib>=0.7.0 in /opt/conda/lib/python3.7/site-packages (from google-auth-oauthlib<0.5,>=0.4.1->tensorboard~=2.6->tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (1.3.1)\n",
      "Requirement already satisfied: google-crc32c<2.0dev,>=1.0 in /opt/conda/lib/python3.7/site-packages (from google-resumable-media<3.0dev,>=0.6.0->google-cloud-bigquery<4,>=1.6.0->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (1.5.0)\n",
      "Requirement already satisfied: importlib-metadata>=4.4 in /opt/conda/lib/python3.7/site-packages (from markdown>=2.6.8->tensorboard~=2.6->tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (6.0.1)\n",
      "Requirement already satisfied: pyasn1>=0.1.7 in /opt/conda/lib/python3.7/site-packages (from oauth2client>=1.4.12->google-apitools<0.5.32,>=0.5.31->apache-beam[gcp]<3,>=2.35->tensorflow-transform==1.6.0) (0.4.8)\n",
      "Requirement already satisfied: zipp>=0.5 in /opt/conda/lib/python3.7/site-packages (from importlib-metadata>=4.4->markdown>=2.6.8->tensorboard~=2.6->tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (3.15.0)\n",
      "Requirement already satisfied: oauthlib>=3.0.0 in /opt/conda/lib/python3.7/site-packages (from requests-oauthlib>=0.7.0->google-auth-oauthlib<0.5,>=0.4.1->tensorboard~=2.6->tensorflow!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,<2.8,>=1.15.5->tensorflow-transform==1.6.0) (3.2.2)\n",
      "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
      "\u001b[0m"
     ]
    }
   ],
   "source": [
    "!sudo -i pip install tensorflow-transform==1.6.0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c174a02-5538-4038-bf1d-8f325172cb01",
   "metadata": {},
   "source": [
    "You can ingnore the dependency resolver errors. Confirm the final message starting with \"Successfully installed ...\"\n",
    "\n",
    "**Now you have to restart kernel from the menu bar: \"Kernel\" -> \"Restart Kernel\".**\n",
    "\n",
    "After restarting the kernel, you can resume the code execution from the next cell."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "36093fa6-c6a9-4556-966d-a8a56121a62b",
   "metadata": {},
   "source": [
    "### Confirm the installed packages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "46b3d806-7153-47e7-a5a2-b763d9763b9c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "apache-beam                            2.46.0\n",
      "tensorflow                             2.7.4\n",
      "tensorflow-cloud                       0.1.16\n",
      "tensorflow-datasets                    4.8.2\n",
      "tensorflow-estimator                   2.7.0\n",
      "tensorflow-hub                         0.13.0\n",
      "tensorflow-io                          0.25.0\n",
      "tensorflow-io-gcs-filesystem           0.25.0\n",
      "tensorflow-metadata                    1.6.0\n",
      "tensorflow-probability                 0.19.0\n",
      "tensorflow-serving-api                 2.7.4\n",
      "tensorflow-transform                   1.6.0\n"
     ]
    }
   ],
   "source": [
    "!pip list | grep -E '(tensorflow|beam)'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83602189-f2af-4bf6-bf5f-7c73f4c6c1af",
   "metadata": {},
   "source": [
    "### Set global flags"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "08532dec-9327-4925-a547-6c6aedaa84e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "PROJECT = 'your-project'\n",
    "BUCKET = 'your-project-babyweight'\n",
    "REGION = 'us-central1'\n",
    "ROOT_DIR = 'babyweight_tft'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8c6ce10f-6be6-4a1f-a8a1-a549480178aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "os.environ['PROJECT'] = PROJECT\n",
    "os.environ['BUCKET'] = BUCKET\n",
    "os.environ['REGION'] = REGION\n",
    "os.environ['ROOT_DIR'] = ROOT_DIR"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "3f0995fc-81e4-41ab-adef-b4cd46cdd947",
   "metadata": {},
   "outputs": [],
   "source": [
    "OUTPUT_DIR = 'gs://{}/{}'.format(BUCKET,ROOT_DIR)\n",
    "TRANSFORM_ARTEFACTS_DIR = os.path.join(OUTPUT_DIR,'transform')\n",
    "TRANSFORMED_DATA_DIR = os.path.join(OUTPUT_DIR,'transformed')\n",
    "TEMP_DIR = os.path.join(OUTPUT_DIR, 'tmp')\n",
    "MODELS_DIR = os.path.join(OUTPUT_DIR,'models')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "01c43032-1b62-429c-89e0-1f86fd536e46",
   "metadata": {},
   "source": [
    "### Import required packages and modules"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "361a3a69-d9e1-4dfd-8260-2541bb356be8",
   "metadata": {},
   "outputs": [],
   "source": [
    "import math, os\n",
    "\n",
    "import tensorflow as tf\n",
    "import tensorflow_transform as tft\n",
    "\n",
    "from tensorflow.keras import layers, models"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "01f1c7ad-9d5c-4737-bc8c-0c0f9594cb0d",
   "metadata": {},
   "source": [
    "## 2. Define deep and wide regression model"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bcb9e128-2849-4b90-a01f-2b83119767c2",
   "metadata": {},
   "source": [
    "### Check features in the transformed data"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51032e84-60cc-479e-8a76-0bb704beb7c6",
   "metadata": {},
   "source": [
    "You can use these features (except the target feature) as an input to the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "7b446898-e7eb-4808-a8db-3a7ca17f3922",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "feature {\n",
       "  name: \"gestation_weeks_scaled\"\n",
       "  type: FLOAT\n",
       "  presence {\n",
       "    min_fraction: 1.0\n",
       "  }\n",
       "  shape {\n",
       "  }\n",
       "}\n",
       "feature {\n",
       "  name: \"is_male_index\"\n",
       "  type: INT\n",
       "  int_domain {\n",
       "    min: -1\n",
       "    max: 1\n",
       "    is_categorical: true\n",
       "  }\n",
       "  presence {\n",
       "    min_fraction: 1.0\n",
       "  }\n",
       "  shape {\n",
       "  }\n",
       "}\n",
       "feature {\n",
       "  name: \"is_multiple_index\"\n",
       "  type: INT\n",
       "  int_domain {\n",
       "    min: -1\n",
       "    max: 1\n",
       "    is_categorical: true\n",
       "  }\n",
       "  presence {\n",
       "    min_fraction: 1.0\n",
       "  }\n",
       "  shape {\n",
       "  }\n",
       "}\n",
       "feature {\n",
       "  name: \"mother_age_bucketized\"\n",
       "  type: INT\n",
       "  int_domain {\n",
       "    min: 0\n",
       "    max: 4\n",
       "    is_categorical: true\n",
       "  }\n",
       "  presence {\n",
       "    min_fraction: 1.0\n",
       "  }\n",
       "  shape {\n",
       "  }\n",
       "}\n",
       "feature {\n",
       "  name: \"mother_age_log\"\n",
       "  type: FLOAT\n",
       "  presence {\n",
       "    min_fraction: 1.0\n",
       "  }\n",
       "  shape {\n",
       "  }\n",
       "}\n",
       "feature {\n",
       "  name: \"mother_age_normalized\"\n",
       "  type: FLOAT\n",
       "  presence {\n",
       "    min_fraction: 1.0\n",
       "  }\n",
       "  shape {\n",
       "  }\n",
       "}\n",
       "feature {\n",
       "  name: \"mother_race_index\"\n",
       "  type: INT\n",
       "  int_domain {\n",
       "    min: -1\n",
       "    max: 9\n",
       "    is_categorical: true\n",
       "  }\n",
       "  presence {\n",
       "    min_fraction: 1.0\n",
       "  }\n",
       "  shape {\n",
       "  }\n",
       "}\n",
       "feature {\n",
       "  name: \"weight_pounds\"\n",
       "  type: FLOAT\n",
       "  presence {\n",
       "    min_fraction: 1.0\n",
       "  }\n",
       "  shape {\n",
       "  }\n",
       "}"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "transformed_metadata = tft.TFTransformOutput(TRANSFORM_ARTEFACTS_DIR).transformed_metadata\n",
    "transformed_metadata.schema"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1844432b-f5c8-4ba9-9def-ea4e8a198a15",
   "metadata": {},
   "source": [
    "### Define wide and deep feature columns"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79453a1b-7b9f-4d4a-a117-1e224e490860",
   "metadata": {},
   "source": [
    "This is a feature engineering layer that creates new features from the transformed data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "f0d043c4-bc3f-4b45-9ed9-78e3a2ecc0e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_wide_and_deep_feature_columns():\n",
    "\n",
    "    deep_feature_columns = []\n",
    "    wide_feature_columns = []\n",
    "    inputs = {}\n",
    "    categorical_columns = {}\n",
    "\n",
    "    # Select features you've checked from the metadata\n",
    "    # - Categorical features are associated with the vocabulary size (starting from 0)\n",
    "    numeric_features = ['mother_age_log', 'mother_age_normalized', 'gestation_weeks_scaled']\n",
    "    categorical_features = [('is_male_index', 1), ('is_multiple_index', 1),\n",
    "                            ('mother_age_bucketized', 4), ('mother_race_index', 10)]\n",
    "\n",
    "    for feature in numeric_features:\n",
    "        deep_feature_columns.append(tf.feature_column.numeric_column(feature))\n",
    "        inputs[feature] = layers.Input(shape=(), name=feature, dtype='float32')\n",
    "\n",
    "    for feature, vocab_size in categorical_features:\n",
    "        categorical_columns[feature] = (\n",
    "            tf.feature_column.categorical_column_with_identity(feature, num_buckets=vocab_size+1))\n",
    "        wide_feature_columns.append(tf.feature_column.indicator_column(categorical_columns[feature]))\n",
    "        inputs[feature] = layers.Input(shape=(), name=feature, dtype='int64')\n",
    "\n",
    "    mother_race_X_mother_age_bucketized = tf.feature_column.crossed_column(\n",
    "        [categorical_columns['mother_age_bucketized'],\n",
    "         categorical_columns['mother_race_index']],  55)\n",
    "    wide_feature_columns.append(tf.feature_column.indicator_column(mother_race_X_mother_age_bucketized))\n",
    "        \n",
    "    mother_race_X_mother_age_bucketized_embedded = tf.feature_column.embedding_column(\n",
    "        mother_race_X_mother_age_bucketized, 5)        \n",
    "    deep_feature_columns.append(mother_race_X_mother_age_bucketized_embedded)\n",
    "\n",
    "    return wide_feature_columns, deep_feature_columns, inputs"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e117d97c-cb4c-48dd-b8e3-98ff22dbfa4a",
   "metadata": {},
   "source": [
    "### Define a regression model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "64ee68e6-a898-48fc-b27e-c8494d1f9617",
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_model():\n",
    "\n",
    "    wide_feature_columns, deep_feature_columns, inputs = create_wide_and_deep_feature_columns()\n",
    "    feature_layer_wide = layers.DenseFeatures(wide_feature_columns, name='wide_features')\n",
    "    feature_layer_deep = layers.DenseFeatures(deep_feature_columns, name='deep_features')\n",
    "\n",
    "    wide_model = feature_layer_wide(inputs)\n",
    "    \n",
    "    deep_model = layers.Dense(64, activation='relu', name='DNN_layer1')(feature_layer_deep(inputs))\n",
    "    deep_model = layers.Dense(32, activation='relu', name='DNN_layer2')(deep_model)\n",
    "\n",
    "    wide_deep_model = layers.Dense(1, name='weight')(layers.concatenate([wide_model, deep_model]))\n",
    "    model = models.Model(inputs=inputs, outputs=wide_deep_model)\n",
    "\n",
    "    # Compile Keras model\n",
    "    model.compile(loss='mse', optimizer='adam') # tf.keras.optimizers.Adam(learning_rate=0.0001))\n",
    "\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "536d4d31-69b2-4f68-a016-94f581724fa1",
   "metadata": {},
   "source": [
    "### Define tfrecords_input_fn"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4494e70-37be-4289-b610-467ff1d21e1a",
   "metadata": {},
   "source": [
    "This function creates a batched dataset from the transformed dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "1a7d90fa-fb1b-414a-bc1b-cd3d313de447",
   "metadata": {},
   "outputs": [],
   "source": [
    "def tfrecords_input_fn(files_name_pattern, batch_size=512):\n",
    "    \n",
    "    tf_transform_output = tft.TFTransformOutput(TRANSFORM_ARTEFACTS_DIR)\n",
    "    TARGET_FEATURE_NAME = 'weight_pounds'\n",
    "\n",
    "    batched_dataset = tf.data.experimental.make_batched_features_dataset(\n",
    "        file_pattern=files_name_pattern,\n",
    "        batch_size=batch_size,\n",
    "        features=tf_transform_output.transformed_feature_spec(),\n",
    "        reader=tf.data.TFRecordDataset,\n",
    "        label_key=TARGET_FEATURE_NAME,\n",
    "        shuffle=True).prefetch(tf.data.experimental.AUTOTUNE)\n",
    "\n",
    "    return batched_dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4baba0e9-39d3-4e3d-b9be-4401a827b414",
   "metadata": {},
   "source": [
    "## 3. Train and export the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "2f27fb6f-53ab-44bf-afd0-c55912a866c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "def train_and_evaluate(train_pattern, eval_pattern):\n",
    "\n",
    "    train_dataset = tfrecords_input_fn(train_pattern, batch_size=BATCH_SIZE)\n",
    "    validation_dataset = tfrecords_input_fn(eval_pattern, batch_size=BATCH_SIZE)\n",
    "\n",
    "    model = create_model()\n",
    "    print(model.summary())\n",
    "\n",
    "    print('Now training the model... hang on')\n",
    "    history = model.fit(\n",
    "        train_dataset,\n",
    "        validation_data=validation_dataset,\n",
    "        epochs=NUM_EPOCHS,\n",
    "        steps_per_epoch=math.ceil(NUM_TRAIN_INSTANCES / BATCH_SIZE),\n",
    "        validation_steps=math.ceil(NUM_TEST_INSTANCES / BATCH_SIZE),\n",
    "        verbose=0)\n",
    "\n",
    "    print('Evaluate the trained model.')\n",
    "    print(model.evaluate(validation_dataset, steps=NUM_TEST_INSTANCES))\n",
    "    \n",
    "    return history, model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "21ef6f29-56d5-45dc-942d-fbc0d800bcc0",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2023-04-19 03:01:35.395469: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/cuda/lib64:/usr/local/nccl2/lib:/usr/local/cuda/extras/CUPTI/lib64\n",
      "2023-04-19 03:01:35.395536: W tensorflow/stream_executor/cuda/cuda_driver.cc:269] failed call to cuInit: UNKNOWN ERROR (303)\n",
      "2023-04-19 03:01:35.395565: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (tensorflow-2-8-20230419-113852): /proc/driver/nvidia/version does not exist\n",
      "2023-04-19 03:01:35.395935: I tensorflow/core/platform/cpu_feature_guard.cc:151] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 FMA\n",
      "To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"model\"\n",
      "__________________________________________________________________________________________________\n",
      " Layer (type)                   Output Shape         Param #     Connected to                     \n",
      "==================================================================================================\n",
      " gestation_weeks_scaled (InputL  [(None,)]           0           []                               \n",
      " ayer)                                                                                            \n",
      "                                                                                                  \n",
      " is_male_index (InputLayer)     [(None,)]            0           []                               \n",
      "                                                                                                  \n",
      " is_multiple_index (InputLayer)  [(None,)]           0           []                               \n",
      "                                                                                                  \n",
      " mother_age_bucketized (InputLa  [(None,)]           0           []                               \n",
      " yer)                                                                                             \n",
      "                                                                                                  \n",
      " mother_age_log (InputLayer)    [(None,)]            0           []                               \n",
      "                                                                                                  \n",
      " mother_age_normalized (InputLa  [(None,)]           0           []                               \n",
      " yer)                                                                                             \n",
      "                                                                                                  \n",
      " mother_race_index (InputLayer)  [(None,)]           0           []                               \n",
      "                                                                                                  \n",
      " deep_features (DenseFeatures)  (None, 8)            275         ['gestation_weeks_scaled[0][0]', \n",
      "                                                                  'is_male_index[0][0]',          \n",
      "                                                                  'is_multiple_index[0][0]',      \n",
      "                                                                  'mother_age_bucketized[0][0]',  \n",
      "                                                                  'mother_age_log[0][0]',         \n",
      "                                                                  'mother_age_normalized[0][0]',  \n",
      "                                                                  'mother_race_index[0][0]']      \n",
      "                                                                                                  \n",
      " DNN_layer1 (Dense)             (None, 64)           576         ['deep_features[0][0]']          \n",
      "                                                                                                  \n",
      " wide_features (DenseFeatures)  (None, 75)           0           ['gestation_weeks_scaled[0][0]', \n",
      "                                                                  'is_male_index[0][0]',          \n",
      "                                                                  'is_multiple_index[0][0]',      \n",
      "                                                                  'mother_age_bucketized[0][0]',  \n",
      "                                                                  'mother_age_log[0][0]',         \n",
      "                                                                  'mother_age_normalized[0][0]',  \n",
      "                                                                  'mother_race_index[0][0]']      \n",
      "                                                                                                  \n",
      " DNN_layer2 (Dense)             (None, 32)           2080        ['DNN_layer1[0][0]']             \n",
      "                                                                                                  \n",
      " concatenate (Concatenate)      (None, 107)          0           ['wide_features[0][0]',          \n",
      "                                                                  'DNN_layer2[0][0]']             \n",
      "                                                                                                  \n",
      " weight (Dense)                 (None, 1)            108         ['concatenate[0][0]']            \n",
      "                                                                                                  \n",
      "==================================================================================================\n",
      "Total params: 3,039\n",
      "Trainable params: 3,039\n",
      "Non-trainable params: 0\n",
      "__________________________________________________________________________________________________\n",
      "None\n",
      "Now training the model... hang on\n",
      "Evaluate the trained model.\n",
      "3000/3000 [==============================] - 132s 44ms/step - loss: 1.0578\n",
      "1.0578476190567017\n"
     ]
    }
   ],
   "source": [
    "train_pattern = os.path.join(TRANSFORMED_DATA_DIR, 'train-*.tfrecords')\n",
    "eval_pattern = os.path.join(TRANSFORMED_DATA_DIR, 'eval-*.tfrecords')\n",
    "\n",
    "DATA_SIZE = 10000\n",
    "BATCH_SIZE = 512\n",
    "NUM_EPOCHS = 100\n",
    "NUM_TRAIN_INSTANCES = math.ceil(DATA_SIZE * 0.7)\n",
    "NUM_TEST_INSTANCES = math.ceil(DATA_SIZE * 0.3)\n",
    "\n",
    "history, trained_model = train_and_evaluate(train_pattern, eval_pattern)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1a0d991-5974-4529-aba6-1f4ede30e5a4",
   "metadata": {},
   "source": [
    "### Visualize the training result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "a162ca81-bdaa-44c7-9e8c-251d5cb55306",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Final RMSE for the validation set: 1.027367\n"
     ]
    },
    {
     "data": {
      "image/png": "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d97JF198wQMPPMC///1v93stWrRg4sSJfPjhh7z88su89dZbDXoOnqbLNJ7gTs4nrzOiu2lERJqa9u3bM3r0aO644w7+9a9/ERgYyMMPP0zLli0ZPXo0APfddx8jRoygQ4cOHDt2jMWLF9O5c2cAHnvsMfr06UPXrl0pLy9n3rx57vcuFBoZ8YRTVmA9fjeN1hkREWmSZsyYQZ8+fbjmmmtISkrCMAy+/vprvL29AXA4HEyePJnOnTtz1VVX0aFDB15//XUAfHx8mDZtGj169GDo0KHYbDZmz55t5unUO42MeILmjIiINHlLlixx/z00NJT333+/xrYn5oecziOPPMIjjzxSn6WddzQy4gmnrDOiOSMiIiI1URjxhBpu7dXIiIiIyKkURjyhxqf2agKriIjIyRRGPEFzRkRERGpNYcQjNGdERESkthRGPOHkkZHjz6ZxKoyIiIicQmHEE06EEU5aZ0RhRERE5BQKI57gvrX35BVYFUZEREROpjDiCXpqr4iISK0pjHjCSYueaWRERETqKj4+npdffrlWbS0WC3PnzvVoPZ6kMOIJNY6MaJ0RERGRkymMeMJJi555HZ/A6tCD8kRERE6hMOIJmjMiIuI5hgEVxea8jNr9O/7WW28RExOD86QR8dGjR3Prrbeyd+9eRo8eTWRkJAEBAfTr14/vv/++3rpoy5YtXHbZZfj5+REeHs4f/vAHioqK3O8vWbKE/v3706xZM0JCQhg8eDBpaWkAbNq0iUsvvZTAwECCgoLo06cPa9eurbfaTkdP7fWIk+aM2DRnRESk3lSWwLMx5hz7fw6BT7NfbXb99ddz9913s3jxYi6//HIAcnNzWbBgAV9//TVFRUVcffXVPPPMM9jtdt5//31GjRpFSkoKcXFx51RicXExw4cPJykpiTVr1pCTk8Ptt9/OlClTmDlzJlVVVYwZM4Y77riDjz/+mIqKClavXo3l+HzH8ePH06tXL9544w1sNhsbN27E29v7nGr6NQojnqCRERGRJi00NJQRI0bw0UcfucPIZ599RvPmzbn00kuxWq0kJia62z/99NPMmTOHr776iilTppzTsT/66CPKysp4//33adbMFZxeffVVRo0axf/+7//i7e1Nfn4+11xzDW3btgWgc+fO7v3T09N58MEH6dSpEwDt27c/p3pqQ2HEE05a9Ex304iI1CNvf9cIhVnHrqXx48dzxx138Prrr2O325k1axY33ngjVquVoqIinnjiCebPn09mZiZVVVWUlpaSnp5+ziXu2LGDxMREdxABGDx4ME6nk5SUFIYOHcott9zC8OHDueKKKxg2bBi//e1viY6OBmDq1KncfvvtfPDBBwwbNozrr7/eHVo8RXNGPOGkRc90N42ISD2yWFyXSsx4nfj3vRZGjRqFYRjMnz+fjIwMfvzxR8aPHw/An//8Z+bMmcOzzz7Ljz/+yMaNG+nevTsVFRWe6rVqZsyYQXJyMoMGDeKTTz6hQ4cOrFy5EoAnnniCbdu2MXLkSH744Qe6dOnCnDlzPFqPwognnLLOyPG7aTQyIiLSZPj6+jJ27FhmzZrFxx9/TMeOHenduzcAP/30E7fccgvXXXcd3bt3Jyoqiv3799fLcTt37symTZsoLi52b/vpp5+wWq107NjRva1Xr15MmzaNFStW0K1bNz766CP3ex06dOD+++/nu+++Y+zYscyYMaNeaquJwognaM6IiIjgulQzf/583n33XfeoCLjmYXzxxRds3LiRTZs28bvf/e6UO2/O5Zi+vr5MnDiRrVu3snjxYu6++25uvvlmIiMjSU1NZdq0aSQnJ5OWlsZ3333H7t276dy5M6WlpUyZMoUlS5aQlpbGTz/9xJo1a6rNKfEEzRnxhFPWGbG4f3Q6DazW2g/ziYhI43XZZZcRFhZGSkoKv/vd79zbX3zxRW699VYGDRpE8+bNeeihhygoKKiXY/r7+/Ptt99y77330q9fP/z9/Rk3bhwvvvii+/2dO3fy3nvvcfToUaKjo5k8eTJ//OMfqaqq4ujRo0yYMIHs7GyaN2/O2LFjefLJJ+ultppYDKOWN02bqKCggODgYPLz8wkKCjK7nF+XfwBe6go2OzyaQ0FZJT2e+A6AXX8bgY+XBqRERGqjrKyM1NRUEhIS8PX1NbscOY0z/Y5q+/2tb0VPOOkyjdcvRkI0b0RERKQ6hRGPqD6B1faLMKI7akREpC5mzZpFQEDAaV9du3Y1u7x6oTkjnnDKyMjPmU8jIyIiUhfXXnstAwYMOO17nl4ZtaEojHjCSYue/XK+qu6oERGRuggMDCQwMNDsMjxKl2k8wfKLbjUMLBaLVmEVETkHjeBeiyarPn43CiOe8MsV+rTWiIjIWTtxGaKkpMTkSqQmJ34353LJSJdpPOGUMGJzhxGHQ2FERKS2bDYbISEh5OTkAK41Mix1WJJdPMcwDEpKSsjJySEkJASbzXbWn6Uw4gknXaYBPZ9GRORsRUVFAbgDiZxfQkJC3L+js6Uw4gnVwkj1tUY0Z0REpG4sFgvR0dFERERQWVlpdjnyC97e3uc0InKCwohHnG7OiCugaM6IiMjZsdls9fLFJ+cfTWD1BI2MiIiI1JrCiCf8Moxw8pwRhREREZFfUhjxhNONjNhOjIxoAquIiMgvKYx4wpnWGdGtvSIiItUojHjCaW7tdc8Z0SqCIiIi1SiMeMIvw0hFEfDz3TSawCoiIlKdwognWCwQ09v1993fAT+PjGgCq4iISHUKI57S9TrXn9vmAmg5eBERkRoojHhK1zGuP/cvh8IsjYyIiIjUQGHEU0LioFU/wIDtX/08MqIwIiIiUo3CiCe5L9V84V5nRA/KExERqU5hxJO6jHH9mZ5Mc2cuoJERERGRkymMeFJwS4gdCMDA0mWA5oyIiIicTGHE07qNBaBfyVJAIyMiIiInq1MYmT59Ov369SMwMJCIiAjGjBlDSkrKr+736aef0qlTJ3x9fenevTtff/31WRfc6HS+FrDQrnw7MRzRyIiIiMhJ6hRGli5dyuTJk1m5ciULFy6ksrKSK6+8kuLi4hr3WbFiBTfddBO33XYbGzZsYMyYMYwZM4atW7eec/GNQlA0tB4MwNW2VTgcmsAqIiLySxbDOPuHpRw+fJiIiAiWLl3K0KFDT9vmhhtuoLi4mHnz5rm3DRw4kJ49e/Lmm2/W6jgFBQUEBweTn59PUFDQ2ZZrntX/hq//zEZnW9Ze8Rm3X9TG7IpEREQ8rrbf3+c0ZyQ/Px+AsLCwGtskJyczbNiwatuGDx9OcnJyjfuUl5dTUFBQ7dWodRmNEys9rXvxLz5gdjUiIiLnlbMOI06nk/vuu4/BgwfTrVu3GttlZWURGRlZbVtkZCRZWVk17jN9+nSCg4Pdr9jY2LMt8/wQEMG+Zj0BiM/+ztxaREREzjNnHUYmT57M1q1bmT17dn3WA8C0adPIz893vzIyMur9GA1tZ/AQAKLyN5pbiIiIyHnG62x2mjJlCvPmzWPZsmW0atXqjG2joqLIzs6uti07O5uoqKga97Hb7djt9rMp7bx1xM81TyS4tPEHKxERkfpUp5ERwzCYMmUKc+bM4YcffiAhIeFX90lKSmLRokXVti1cuJCkpKS6VdrIHfOPAyCk7AA4qkyuRkRE5PxRpzAyefJkPvzwQz766CMCAwPJysoiKyuL0tJSd5sJEyYwbdo098/33nsvCxYs4IUXXmDnzp088cQTrF27lilTptTfWTQCxT6RlBne2IwqyNfoiIiIyAl1CiNvvPEG+fn5XHLJJURHR7tfn3zyibtNeno6mZmZ7p8HDRrERx99xFtvvUViYiKfffYZc+fOPeOk1wuRzcvGfuP4pamje80tRkRE5DxSpzkjtVmSZMmSJadsu/7667n++uvrcqgLjpfVQqoRRScy4OgeaD/s13cSERFpAvRsmgZis1pJNaJdPxzdY24xIiIi5xGFkQZyYmQEgFxdphERETlBYaSB2KwWUp0n5oxoZEREROQEhZEG4hoZOX6ZJi8DKsvMLUhEROQ8oTDSQDpGBXKUIAoMf8CAY6lmlyQiInJeUBhpIBd3aMHIHjHueSNl2btMrkhEROT8oDDSQCwWC8+O6U6WV0sAFv9U81OLRUREmhKFkQYU7O9N5269Acg/sIPvttX85GIREZGmQmGkgcW17wFAgjWLh7/YQk6hJrKKiEjTpjDS0MJcT+9tb8smt7iCBz/dXKuVbUVERC5UCiMNLbwtAGHGMcK8yli66zDr04+ZXJSIiIh5FEYamm8wNIsA4IY2FQAsTTlsZkUiIiKmUhgxQ3g7AIaGFwCwdPcRM6sRERExlcKIGcJd80a6+bpCyOYDeRwrrjCzIhEREdMojJjh+MhIYNF+OkYGYhiwfI9GR0REpGlSGDHD8TDC0T0M7dAcgGW7NG9ERESaJoURM5wII7l7Gdr+eBjZfVi3+IqISJOkMGKG0ATAAmX59Isw8PW2kl1Qzq7sIrMrExERaXAKI2bw9oXgWAB8C1IZkBAO6FKNiIg0TQojZjm++Jlr3kgLwHWpRkREpKlRGDGLexLrXi4+Pol1VWoupRUOE4sSERFpeAojZvnFyEjbFgHEBPtSUeVkVepRc+sSERFpYAojZvnFyIjFYvn5Us0urTciIiJNi8KIWU6MjOTuBadT80ZERKTJUhgxS3AcWL2hqgzy9jO4bXOsFtiTU8TBvFKzqxMREWkwCiNmsXlBq76uvy/9O8H+3vSMDQF0i6+IiDQtCiNmuvJvgAU2fQypP7ov1SxNURgREZGmQ2HETK36Qt9Jrr/Pf4DL2oUA8OPuw5RX6RZfERFpGhRGzHb5Y9CsBRxJoVv6B0QE2imucJC8V7f4iohI06AwYja/ULjyGQCsy57n+rauEZGF27PNrEpERKTBKIycD3r8FuIvgqpSJhW8Dhh8vyNbT/EVEZEmQWHkfGCxwMgXwepN80NLuNZnHdkF5Ww5mG92ZSIiIh6nMHK+aNEBhtwHwJPe79OMUl2qERGRJkFh5Hxy0QMQmkCo4whTvT5TGBERkSZBYeR84u0HI18A4BbbAmzZm8nILTG5KBEREc9SGDnftLscuo3DZjF41vsdvt92yOyKREREPEph5Hw0fDrlXgEkWvdhWfOO2dWIiIh4lMLI+SgwkqIhfwVgXP4MCrLTTS5IRETEcxRGzlPhQ+9kh60DgZZSiufcA+mrIGMNHFgLB9dDeZHZJYqIiNQLL7MLkBpYrazs8hjtN08gOmsxvLu4+vu+IZA0BQb8AXyDTSlRRESkPmhk5DzWs98Qnqkaz34jmorAOBzBrTFC4sA/HMryYPHf4OXusPTvUPbzAmmZ+aXc8/EGnvtmp1ZxFRGR857FaATfVgUFBQQHB5Ofn09QUJDZ5TQYp9NgwPRFHC4sd2/z8bISE+jNnS22MK5oFt65u11v+AbDqH+w2DaYqf/ZyLGSSgBeuD6RcX1amVG+iIg0cbX9/tbIyHnMarXwyMjOdIkOIqyZDwAVVU72Hyvn4V0d6J7zJF+0eZqq8I6ukZFPbyHlw/spKCkj/Hj7J/+7jeyCMjNPQ0RE5Iw0MtKIlFc5yCkoZ8/hIt5YvJfV+3MBCPKBR/0+4/ryLwDYF9CHqNs+4qZZe9h0IJ/LOkXwzsS+WCwWM8sXEZEmRiMjFyC7l43YMH8u7RjBJ38cyMxJ/egaE0RBBTyY/xse4H6qbP60KVqH/4zLefWiKnxsVn7YmcPn6w+aXb6IiMhpaWSkkXM6DRZsy2LVvqPcNqQNcY40mD0ecvcCsK/5pUw5eAUZ9nYsvP9iooJ96/T5ry/Zw87MQv53XA/8fGyeOAUREblA1fb7W2HkQlSWD18/CJv/A7h+vd85+vBTq9t54g831fpyza7sQoa/vAzDgAeu6MDdl7f3YNEiInKhURgRyNkJy57H2Po5luOhZIH3FazqMJXOCbH0ig2hbYsArNbTh5N7Pt7AV5tcz8Zp5mNjyYOX0iLQ3mDli4hI46Y5IwIRneA372CZvJrU6KtxGhauqlzInVtv4vsv3uGKl5ZxzT+Xk19aecque3KK+O9mVxCJC/OnuMLBPxbtaugzEBGRJkBhpClo0YGEP35M3o1fUhSYQKQlj7d8XuJ1n1fIzszg8S+3nrLL64v3YBgwrHMkf/9NDwA+Xp3BnhwtQy8iIvVLYaQJCet8MQH3rIQh94PFxtXWlSyy/5mwLe/w1fpUd7v9R4qZu9F19809l7djYJtwhnWOwOE0+N8FO8/q2Bm5Jbz94z4qqpz1ci4iInLhUBhparx9YdgTcMcPENWdEEsxj3l/QOJXV3F07edgGLy+ZA9OAy7p0JweIRVQXsjDIzphs1pYuD2b1am5dT7s/Z9s5G/zdzBzReqvNxYRkSZFE1ibMqeDqvUfUDj/CUKNYwCURfflx4MQSzbtfY5gqyoFmx2ueJJHDg3iw9UHSIwNYe6fBtX6rpzthwq4+pUfAejRKpivpgzx2CmJiMj5QxNY5ddZbXj1vYX8O1bxhjGWMsMb38y1XGFdSydrhiuIADjKYcHDPFbwOK19CtmUkcecDbVfRO3DVWnuv28+kE/60ZL6PhMREWnEFEaE+JhIQkc+yaXlL/Jc5Y08UjmJlGEzYco6eCQHRr4AXr747F/MAp+HuNy6jj9/uol/fL8bh/M0A2uVZVBwCBxVFJZVMvd4cIk4flvw/C2Zp61j1b6jjHtjBVsP5p/2fRERuTDpMo0AYBgGf/xgHd9tz2ZgmzBm/yGpeoOcnfD57ZC9BYBMI4wywxuLjz8xzUPx8bJB8RHXq6LQtU9wHD+2/hMTVreibUQQkwbH89c5W+nWMoh5d190yvGvevlHUrILGdEtijd+36chTltERDxIi55JnRWWVTJ7dQbXJEYTHex3aoOqclj0FCS/WqfP3ehsw6H+f2XAJaPo/+wiHE6DJX++hPjmzdxtFqfkMGnGGgDsXlbWP3oFzexe53Q+IiJiLoUR8ZzCLCjMIvPoMd5YuJVDR45hxcnYIYlcNaAH+IeDzZsD3/wfIetfJ8BS5tqv0zVMKRjPvH0GDw7vyORL27k/8sa3klm57+e7dF65qRfXJsY09JmJiEg90gRW8ZzAKIjpSXT3S3nknsm0GjiO75z9uHOZD5/vt4NfCPg04/nSa7mk/CWSw0aDxQo75/H80bvpY0lh/uaf541szMhj5b5cvKwWxvZuCcD846u/iojIhU9hRM6Jj5eVx0d14ZZB8QA8+NkmvtmSyZGicr7ZksURggkY+0+4awVEdMGv/Aizff5Gv5xPST3sWs31rWWuJwyP7tmS24e0AWBxymGKyqtMOScREWlYCiNyziwWC49d04Xf9m2F04B7Zm/g4c+3UOFwktgqmO6tgiGiM9z+PXQbh7fFwZPe71Hx6e2kZR7mm61ZAPxhaBs6RwfSpnkzKqqcLNqRbfKZiYhIQ9AMQakXVquF6WN7UFLhYN7mTL4/HiTGD2z9cyOfZjDuHTY42tB9+wt0zPmGyre68L13OKX+MXRc8x0ExzI1xs6/jzpZssGX0YkxYLGAYUBlKZQeA0cFhMa7touISKOnCaxSryodTu78YB2LduYQ5OvFqv8Zhp+PrVqbY8UVTHn2FV7wepUoy7Ezfp5hD8Ti7Q+lea7F107oMAJ+844r4IiIyHnJYxNYly1bxqhRo4iJicFisTB37twztl+yZAkWi+WUV1ZWVl0PLY2At83Ka+N7c9+w9rz6u96nBBGA0GY+eLUdypDyfzC0/CUeDX4W49pX4eKHIPEmjLgkDlvCAbCUF0JRtjuIGFYvHFhh1zfw3igoOnzK5+/JKeS2mWv4cGXaKe+JiMj5p86XaYqLi0lMTOTWW29l7Nixtd4vJSWlWiqKiIio66GlkfD1tnHfsA5nbDOyRzRLdx0m3Yhk8BW9sXSLdr9nAT5YuIt/LdrG9W0c/G1Ue/ALY+sxK+Pf20rb8u287fN/hB1cB+9cAb//HMLbArAzq4Dx/17F0eIKFu3MYU9OEY9e0wWbVZd0RETOV3UeGRkxYgR/+9vfuO666+q0X0REBFFRUe6X1aq5s03Z8K5RRAf70isuhCu6RJ3y/jU9oinHh0/S/MkP6cL6ggBumrmV/LIq9vp25TcVT5BuRMCxVHjnSjiwjq0H87nprZUcLa6gZYhr0baZK/bzp1nrKK1wNPQpiohILTVYIujZsyfR0dFcccUV/PTTT2dsW15eTkFBQbWXXFiC/bxZ/tBlfPrHpNOOWnSIDKR9RACVDoPnv93JhHdWU1heRf/4MJb95VL69O7PuPIn2OKMh5IjGG9fTuW/r+C35V9wdVQhX99zEW/8tiNXem1gUMpz5D/XBedz8bB/eYOfq4iInNk5TWC1WCzMmTOHMWPG1NgmJSWFJUuW0LdvX8rLy3n77bf54IMPWLVqFb179z7tPk888QRPPvnkKds1gbVpefn7Xbz8/W73z0ltwnnnlr74+3jhdBo89tVW5qxM4QXvN7nKtqb6ziFxrpViHRXVNju9/LD+/nOIH9wQpyAi0qQ1yHLwtQkjp3PxxRcTFxfHBx98cNr3y8vLKS//+c6JgoICYmNjFUaamN3ZhVzx0jIALmrfnLdu7lttQqxhGDwzfwdvL08liqPcFpHCpPDteKX9CM5KV6PgOPJbXcxzu1syonQ+Q21bcHr5Y735c2g9yIzTEhFpMmobRkxZZ6R///4sX17zcLndbsdutzdgRXI+ah8ZyORL21JQWsVfR3bG17v6nTkWi4W/juxM63B/Uo+UMH747/Dy8YKyAshYDSGx0LwDwRYL9xWUMenffSDvKYayBecH47De/AW0Tqrh6CIi0lBMCSMbN24kOjr61xtKk/fg8E5nfN9isXBzUnz1jb5B0H5YtU2RQb68/8eLue3tJ7AcfZyL2Irjw3HYbvgA2l1ORZWTXdmF5BSW0a1lMBGBvvV8JiIiUpM6h5GioiL27Nnj/jk1NZWNGzcSFhZGXFwc06ZN4+DBg7z//vsAvPzyyyQkJNC1a1fKysp4++23+eGHH/juu+/q7yxEaqF5gJ33/jCUO955EmvOYwxmG3w4lh1enXi1bATfVPXBeXxOd0LzZvSLD6VffBhXdo0i2M/b5OpFRC5cdQ4ja9eu5dJLL3X/PHXqVAAmTpzIzJkzyczMJD093f1+RUUFDzzwAAcPHsTf358ePXrw/fffV/sMkYYS4u/Du3dczJ0z/sb+g6/yG9tSOlft5DWvnaR7RTLPfg3LC6PIPBrGV0fC+M/aA7y5dC8L7huKt023o4uIeIKWg5cmqaSiilcW7SGw8ijDS74iIXU2tvK8U9rlEcB+ZwShCT1p3akvRHSByG4Q0KLhixYRaWQa5G6ahqIwIh5XUQwbZrmWmc8/CAUHoaLo9G0tNhj+DAy8q2FrFBFpZBRGRM6FYUB5AUcz9/H4O3NpRzq3tCslpGAX5O51tRn7b+jxW3PrFBE5j3nsQXkiTYLFAr7BhCf0wqf7dbxc9Rue8H0I7lkPSVNcbebeBXsWmVuniMgFQGFE5FdMGpwAwPwtmeQUlMEVT0P368FZBZ/cDAfXm1yhiEjjZso6IyKNSfdWwfRtHcratGN8uDKNqVd2hNGvQ/Fh2LeE0pljeSb6HxzzjcXLasHLasXPx8qYni3pGx9mdvkiIuc9zRkRqYX5mzOZ/NF6wpv58NPDl+HrbaOk8BiH/3kFrSt2U254s8+IYp8RzT4jhj3OGBY6+zK8V1umjehERJAWURORpue8Xg5epLG5smsk0cG+ZOaXMW9zJkM7NOf297ZxqGAqs+zT6WhJp7Mlg85kuPdJdUYyZeO9XLoti3sub8+kwQn4eOnKqIjIyTQyIlJLry/Zw98XpNC2RTPKKp0czCslrJkP//59L/oE5cPRvXB0NxzdA7u+hYKDVOLFU5W/5wPHFbSPCOTdW/oRG+Zv9qmIiDQI3dorUs+OFVeQ9NwiyiqdAMSH+zNzUn/imzc7tXFJLnw5GVK+BmCRZSCPlI6nd+AxnupXSXj+NjicAn0nQf87GvI0REQajMKIiAc8MncLH65Mp0/rUP49oS9hzXxqbmwYsPINWPgYOCtraGSB3/0HOlxJaYWD4ooqmgfoidUicmFQGBHxgNIKBytTjzKobTh2L1vtdjq4Dj67DY6lkm2NYF1lPLu92jGxTQkhe+dS5R3I/8a9yazdXlQ6nMy4pT9D2jf37ImIiDQAhRGR88nxFV3znf5MnLGajRl5hNoNZlqeJJFdpDhbcV3FU5TgS0SgnW/vG0romUZdREQaAa3AKnI+Ob6ia7C/Nx/ePoD+CWEcK7dwR9m9HCaEjtYD/NjxU9o29yensJyHv9hMI/j/CSIi9UJhRKSBBdi9eG9Sf54Y1YX/nXQlobd8DFZvwtO+YVbnlXjbLHy7LZvZazJ+/cNERC4Aukwjcj5Y8w7MnwpAdlB3Xj/am0XWQbx3zzW0bRFgcnEiImdHl2lEGpO+t8Kgu8FiJbJgC096v8dS653kvzWKym3zXHNOREQuUBoZETmfFGbBtjlUbPwEn6wN7s25Yb3wvuppAjtcZGJxIiJ1o7tpRBq5H1etYut//8kttm/xs1QAsNqexJ7uD3DxkCG0DPEzuUIRkTNTGBG5AGzMyOPHdZtpu+2fDK9YiM3i+s91u7M1qcH9CO9+Fb0uugq7X6DJlYqInEphROQCk71vExULHic2Z3G17eV4sz1oKAf7PUy3zl1pHe6PxWIxqUoRkZ8pjIhcqIoOc2TLd2Rt+IYWOSuI5CgAxYadf1SN5Su/MfRrG8m9l7ejXYRGTETEPAojIk2Aw+Fk85qlNP/pcWILNwGQ4mzFo5WT2Obdjb//JpGRPaJNrlJEmiqFEZGmxDBg08cY3z2KpeQIAJ87LuKZyvFcNySRh0d0wtumO/lFpGFpnRGRpsRigZ6/w3L3Wuh7KwYWxtl+5Af7AxQlv8vv30omp6DM7CpFRE5LIyMiF6ID62DevZC1BYB1zva8ZJ1Ipz6XMmFQG+LC/U0uUESaAl2mEWnqHFWw+i2cP/wNa2UxAPmGP6udnclt0Y+OA0aQ2G8oFqsGSEXEMxRGRMQl/yDG90/g2DEfr6riam+t9erNoYtf4MqBifh620wqUEQuVAojIlKdowqyNnFk22KObl1EfP4a7JZKco0AnrXdRcuk3/L7ga1pEWg3u1IRuUAojIjIGRVmbKX8k1tpXpQCwOyqS3jOuIWLu8czfkBr+sWHavE0ETknCiMi8uuqynEs+hvW5H9iwWC/M5L7K//EBqM9HSMDGT8wjnG9W9HM7mV2pSLSCCmMiEjtpS6DOXdCwUEc2HjdeR0vV4zGgY2WIX787bpuXNoxwuwqRaSR0TojIlJ7CUPhrp+g2zhsOLjb+hnJkc/TPyiPg3mlTJqxhntnb+BoUbnZlYrIBUhhRERc/ELhN+/C2LfBHkRE/mY+MR7kw4Tv6GTN4MuNhxj24lLmbDhAIxhQFZFGRJdpRORUeemuyzZpP7k3pVlj+ax8AN84+xPeujtPjO5G52j99ygiNdOcERE5N04HbJsDWz+H3QvBWel+K89oxiajLUZMH/oOuZKA9kMpsfiy7VABmw/kU1bp4Kb+cYQ18zHxBETEbAojIlJ/SvNg53zY9gVG6o9YHNXnjhTjxxeOIcyqupydRhwArUL9eOvmvnSJ0X+zIk2VwoiIeIajErK3snfDEvZtXErnii20shxxv73H3pXZxjDeL+iN1dvO33+TyLWJMSYWLCJmURgREY+rdDhZtD2LFkdW0fXQ5/ju/QacVQDkWUN5u3wYHzqG8duhifxleEe8bJozL9KUKIyISMMrzIYN78Oad6HwEAClhg+fOy5isd+VVEb0oGV4IHFh/rQO96djVCDx4c2wWbXSq8iFSGFERMzjqHRNfk1+FTI3uTeXGHY2ONux1ujAGmcnVjs74eXjS6eoQLrEBBHi50NeaQV5JZXkl1ZSXunkgSs7MKBNuIknIyJnS2FERMxnGJD2E5Ur3sSyfyleFQXV3i4y/PjB2ZMFjn4scfakBN9TPqJDZAAL7h2K9VdGTwzDYNuhAj5bd4DCsiqeGt1Vy9iLmKy239/6L1VEPMdigfgheMcPAacTDu+EjJWQvhL2LSWgKItrbclca0umyuJDSlASKS3Hkh89hEB/X5787zZ2ZRfx3fYsrooDts8FwwkD7gSrDYCjReXM3XiIT9dmsDOr0H3oDpEB/PHituact4jUiUZGRMQcTiccXAc7/ws7/gu5+35+LzgOek/g3bxEdqxeyHi/VSRWbcbC8X+uOo+CsW+zIr2IW2euoazSCYCPl5WuMUFsSM8jOtiXZX+5FG9NmhUxjS7TiEjjYRiQvQ02fAibPoayvNO3a9kXsjaDo4KquCFcmXkn+wqtdI4O4nf9Y7k2sSW+PlYGP7eYI0Xl/OPGnozu2bJBT0VEfqYH5YlI42GxQFQ3GPEcPLATrnsL4pIAOOyXwN8rf8sfwt7FuP17+P3n4BOIV/pyXil/hN5hlXx+VxI3J8UT7O+N3cvGxKTWALyzPFXP0RFpBBRGROT84u0HiTfArQvgr9kYdyXzjmUs3x3y5ac9RyFhKMuHzOSIEUQ3634+8noc/wPLoaLE/RHjB7bG7mVl84F81uw/ZuLJiEhtaAKriJy/vH2J8Iab+scxc8V+XvlhNx0iA7h7iZPgisf5Muj/CC7YD++PBqs3xPSEuCTCIrsyPX4fm1MzOfDVIvp3D3c9lTgk7ueXbzCUHoPCLA5kpLJ0/TYqIhL53TXDsHvZzD5zkSZFc0ZE5LyXmV/KxX9fQoXDScfIQFKyC+kSHcTcCW3wWfI07FsChZl1+1Crl3u12BMqDRvvNruNKyc9TkKLgPo7AZEmShNYReSC8j9ztvDRqnQAfGxW/nv3EDpGBbreNAzIS4O0ZEhfAcfSwNufVQdK2V8A8VFhDIg0XNvz0qHk52fp5BoB5Bih+PnaaV2xB4CvjSQqR/6D0f071ljPobxS1uzPJf1oCVd2jfq5FhFxUxgRkQtKRm4Jl/zfEhxOg/+5uhN/GPrra4is2HOE3729Cj9vGz8+dCkb0/P4ZG0GyTvTCXQWcZQg+rSJ4qERnejZKpiCJf/Ef+kTeOFgt7Ml/2nzDO2jQwjP20xE/hZaFGzlqBHEGxXXMK/w5+N7WS3cNiSBey9vh//RbVB8GGJ6Q7OGXzn2h53ZeNusXNS+RYMfW+RkCiMicsH5cuNBMnJLuOuSdrV6no1hGFz9ynJ2ZBbg6211r0cC0Kd1KPde3p6L2jfHYvn5sxxpKymd9XsCKg6f8bNXOTvxZcgE9gf0Zse+/Vxn+4nf+SyjnZH2c6OwNtCqP8T2wxHalj2lAfyU482S/eUUlFVxZZcIrusWSrRPGZTmgV8IBMaA9ezuLfhmSyZ3zVqP1QJzJw+mR6uQs/ockfqiMCIiAny+7gAPfOp6Pk5YMx/G9mrJDf1iaR95hssqRYcpmDWBoMwVVFp8OODbgQPNunLQvzOdK7fRPftLrM4KV9uILjiP7MbqrASg3PDmiK0FLZ2Havz4YsNOMX4EU4zdUlntPcNmxxIaD2EJEN4O4odA68Hge+Z/+3ZnFzLmtZ8ornAA0CU6iC+nDNaib2IqhREREcDpNPjP2gyC/by5vHMkPl61/HI2DCg4CM0iwMun+nv5B2H5S7D+PXC4Qokzqiff+17Bw7s6kOtsRjBF9LLuoZd1N4mWfcRYjhBlPUYQJaccqtKwkU8zginG2+I4tRSLDUvLPtDmEojpBaHxENoafJoBUFBWyehXfyL1SDH948PYlVNIXkklD13Vibsu0ZL4Yh6FERERT8s/CHsXuQJCVHfANbdl84F8HIaBw+nE4QSnYdApKpCuMcHYqkqgMAsqisAvlIPlvszdls+8LVnsy84jwjhMa0sOrS3ZdLGkMci6lQRr9umP798cIzSelXkhrMgLId+/NVNvHMHio6Hc/0UKdi8r3943lPjmzRqwU6ozDIOcwnIig059CKJc+BRGREQamUqHk/TcEvbmFLH3cDEpWQVsOZhP+ZH9JFm3Mci6jbaWQ8RZcgixFNf4OYaXLxu8evKfwm4UxF7Ga3+82jUvprIU55G97Ny2gcrcdOJ9iwhy5GIpyoGSoxAcC9E9ITrR9fILgSO7IHs75GyDwylQcfy4htM1euTlAxFdj+/TA8Lbg821hFVGbgkPfLqJ1am5XNU1iqfGdCUisBahxDBcy/4fSwMvX/D2df3pZYfKMqgohPJCKC9ytQ+MhqBoCIoB3xDXir41KS+E1GUQGAUt+9Tq9wKAo9K1r1/omT9fqlEYERG5QBSVV7HtYD4bMvL4Yv0BdmUXEUQxsZbDxFmySbBk8ds2FcST6QoPpbnV9s8LbE+wpQRLwcE6HdfA8vPDCWvLyxejRSfSbXHMyWjGtspo9hnRFBl++Pj6M/Xqnozpm4Dl5Em6hgGZm2DbHNfTmY/tr9txjyu3+JIf1IHmnS/CGjcQ4ga6LmftWgBbv4DdC8FR7mrcejAMmQrtLv85YFRVQNpySFng6suiHCjKcoU1gIBI16MKWg9yfbZfGORsdz1bKXsbHEuFyK7Q+VpIGOoKUCeU5UPqj5Ce7ApO7YZB8w6nDzeGASW5rs/LTcVxdB8ledkEtu7pmhTdvMNZT3RuSAojIiIXIMMwWJ+ex8er05m3+RBllU4mJrXmydHdTjRwfSnu+obsNXOJLNxabf98w580Ysj3jWFPSTOyncEcNkLIpxmtLVl0s+6nm2U/bS2HsFoMCgx/dhqxpDhjSTFiKaAZnWNCGNSuBV1bhuBVWQRZW1xBImuL6/LTr3BiAS9frN5+ruX/vXyhqsw1R+cELz/XpS9Hheu9ylKoKneNktgDwScQ7AGu8y3MxJF/EFtZDUv/W73B+YuJwqHxrktsJ7ZFdYfEm+DAWtjzPZQX1OE3cgb2IOgwHEITIHWp6/ONk+YEBce5wlB0ouv8c/fB0b2Qmwrl+TV/tm+w68GRMb2gRSdo0REjvB17jjlIaN4Mr19OXHZUuQLTgdWQscb1v4/Q1hA7wBWoohOrh6Z6pDAiInKByy+tZFd2IX3iQrGe5lbnKoeTW/45D/+c9Rwxgilo1ppxg3swPqk1Qb7elFRUsS7tGMl7j7Iu7Rj5pZUUV1RRUu7AWVGEn6OYYp8WNLN74W/3wgLszvk5bLQItDOscyTF5VVkF5SRk1+CT8F+4p0ZdLAeYmR0AR1th7Ac249RUYTl5C/ikxhefhTGXcbWkMtYVJXIgWILTsMVwJwGWC1wUfsWXN+3Ff4+Pz/NZF3aMW57bw2lJcX0CSkitmQn3Z076GfbTQdLBhYMnCEJ7I8eztfOAXxxMJR473z+5LuAXoe/dM3j+aVmEdDxKtcISGCUazQkIBK8/eHQBkhfgTMtGSN9FdaqMipC22GJ7IJXdDesoa0hbQXsnAdFp5nrc+IOqWNpkPaTewJ0TSr8o9hSEsbeqhYU4E+ibT99vPdjrSo9tf+wcMDZHIu3L5FBvnjbLMcnYh+Cypov62GzQ8vecMVTENv/jPXUlcKIiIiw73ARryzaTVLbcMb0annOz93Ze7iI/6zJ4LN1BzhafPov0rYtmvHSDT1PXefEUcnezCO8MH8Dm/dn40sFflTgSwXRQd4sL4klt9LntJ/5S8F+3tw8sDUTB8Wz+UAekz9aT1mlk56xIbx7Sz+Ky6t48LNNrNyXSyAl9AirYnV+EJWnyUIhFHK7z0Iu89tDbmh3jsVegb11P1qFBWD3tpJfWkl+aSUFpZUcKaogJauAHZmFpGQXUlVVhRUnVccf82axQIifN21aBNAtOoCL/PbTs+hHgp3HcMYNoir+EoyQOACa+diwVJbA/uWu0Zije13PTAprA+FtMUITmL3HxqPz91LldE2AtntZ2XQgnxFdwnljmC8cWAPZW+FwCpVZ2/GuOMNIij3INUemVT/XSFDuPshY5XqduAR153L3ROz6ojAiIiIeU1HlZNGObDYdyKd5gA8RQb5EBtqJCPIlLsz/Vxely8ovY+H2LBZsy2LlvlwcTtdXUYDdi64xQfRoFXz8c6xYLWC1WMgrrWDWqnTSjrpGMny8rDicBg6nwaUdW/Da+N7uEROn0+C95P08981Oyqtci93FhflzSccWXNS+BQWllfy4+zA/7j5SY6j6Nc18bIQF+JBXUklhWdWv7/ALAXYv2kUE0C4igPYRAUSH+FFSXkVhWRWFZZWkZBfy7TbXyMrIHtE8/5sepOeWcM0ry6lyGrw9oS/DukQCcLiwnBEvL4XiI0zo6CAzr5i9x0ewrk2M4YaLEykPaUdGXjkHjpWSVVBGyxBfusUEExFodwWhjFWQeCNY6/chkQojIiLSKOSVVLAxI4/YMH8Swpud9pLTCQ6nwcLtWfxr2T42pOcB8Js+rZg+tvtpF3hLP1rChoxj9GgVQsJpbnF2Og12ZBWwLu0YB46VcuBYyfE/S6l0OAn283a/Qvy9adcigC4xQXSODiI21N9da6XDSV5JJUeKyknJKmTboXy2Zxaw7VABeSWVpxy3NiwW+MvwTtx5cRv3KsHTv9nBv5buo2WIHwunDsXP28akmWtYknKYTlGBzJ08GIsFpn+9k5kr9gPg522j9HTDQkDzADtdYoLoGhPEb/vGnraPzoXCiIiIXLBcE3mPkVNQzlXdoqot6X8+MQyDskpntRtmnIbBgWOl7M4uYk9OEbtzCskpKCfA14tAXy+CfL0J9PXisk4R9I0Pq/Z5JRVVXPHiMg7mlfLHi9sQHeTLE//djo+Xlf9OGVLtgY0Ltmby4Geb3aM2of7etAr1JzLITtrREvYeLsL5iwTw2Z1JpxzvXCmMiIiIXIAW7cjmtvfW4mW1YLVaqKhy8uS1XZk4KP6UtoVllRzKKyMmxJdAX+9q75VWONiZ5Rq92XaogL+O7EyA3euUzzgXtf3+rvNNysuWLWPUqFHExMRgsViYO3fur+6zZMkSevfujd1up127dsycObOuhxURERHg8s6RXNU1iiqnQUWVk8s6RTAhqfVp2wb6etMxKvCUIALg52OjV1wovx/Ymulju9d7EKmLOoeR4uJiEhMTee2112rVPjU1lZEjR3LppZeyceNG7rvvPm6//Xa+/fbbOhcrIiIi8Pi1XQjx9yYqyJe//6bHeXuZqrbO6TKNxWJhzpw5jBkzpsY2Dz30EPPnz2fr1p8X3rnxxhvJy8tjwYIFp92nvLyc8vJy988FBQXExsbqMo2IiMhxeSUVWK0Wgk4z6nG+8NhlmrpKTk5m2LBh1bYNHz6c5OTkGveZPn06wcHB7ldsbKynyxQREWlUQvx9zusgUhceDyNZWVlERkZW2xYZGUlBQQGlpaeuIAcwbdo08vPz3a+MjAxPlykiIiImMW+2yhnY7Xbsds+sky8iIiLnF4+PjERFRZGdXX19/uzsbIKCgvDz8/P04UVEROQ85/EwkpSUxKJFi6ptW7hwIUlJSZ4+tIiIiDQCdQ4jRUVFbNy4kY0bNwKuW3c3btxIeno64JrvMWHCBHf7O++8k3379vGXv/yFnTt38vrrr/Of//yH+++/v37OQERERBq1OoeRtWvX0qtXL3r16gXA1KlT6dWrF4899hgAmZmZ7mACkJCQwPz581m4cCGJiYm88MILvP322wwfPryeTkFEREQaMy0HLyIiIh5x3qwzIiIiInImCiMiIiJiKoURERERMZXCiIiIiJhKYURERERMpTAiIiIiplIYEREREVMpjIiIiIipFEZERETEVAojIiIiYiqFERERETGVwoiIiIiYSmFERERETKUwIiIiIqZSGBERERFTKYyIiIiIqRRGRERExFQKIyIiImIqhRERERExlcKIiIiImEphREREREylMCIiIiKmUhgRERERUymMiIiIiKkURkRERMRUCiMiIiJiKoURERERMZXCiIiIiJhKYURERERMpTAiIiIiplIYEREREVMpjIiIiIipFEZERETEVAojIiIiYiqFERERETGVwoiIiIiYSmFERERETKUwIiIiIqZSGBERERFTKYyIiIiIqRRGRERExFQKIyIiImIqhRERERExlcKIiIiImEphREREREylMCIiIiKmUhgRERERUymMiIiIiKkURkRERMRUCiMiIiJiKoURERERMZXCiIiIiJhKYURERERMpTAiIiIiplIYEREREVMpjIiIiIipFEZERETEVAojIiIiYiqFERERETGVwoiIiIiYSmFERERETKUwIiIiIqZSGBERERFTnVUYee2114iPj8fX15cBAwawevXqGtvOnDkTi8VS7eXr63vWBYuIiMiFpc5h5JNPPmHq1Kk8/vjjrF+/nsTERIYPH05OTk6N+wQFBZGZmel+paWlnVPRIiIicuGocxh58cUXueOOO5g0aRJdunThzTffxN/fn3fffbfGfSwWC1FRUe5XZGTkGY9RXl5OQUFBtZeIiIhcmOoURioqKli3bh3Dhg37+QOsVoYNG0ZycnKN+xUVFdG6dWtiY2MZPXo027ZtO+Nxpk+fTnBwsPsVGxtblzJFRESkEalTGDly5AgOh+OUkY3IyEiysrJOu0/Hjh159913+fLLL/nwww9xOp0MGjSIAwcO1HicadOmkZ+f735lZGTUpUwRERFpRLw8fYCkpCSSkpLcPw8aNIjOnTvzr3/9i6effvq0+9jtdux2u6dLExERkfNAnUZGmjdvjs1mIzs7u9r27OxsoqKiavUZ3t7e9OrViz179tTl0CIiInKBqlMY8fHxoU+fPixatMi9zel0smjRomqjH2ficDjYsmUL0dHRdatURERELkh1vkwzdepUJk6cSN++fenfvz8vv/wyxcXFTJo0CYAJEybQsmVLpk+fDsBTTz3FwIEDadeuHXl5eTz//POkpaVx++231++ZiIiISKNU5zByww03cPjwYR577DGysrLo2bMnCxYscE9qTU9Px2r9ecDl2LFj3HHHHWRlZREaGkqfPn1YsWIFXbp0qb+zEBERkUbLYhiGYXYRv6agoIDg4GDy8/MJCgoyuxwRERGphdp+f+vZNCIiImIqhRERERExlcKIiIiImEphREREREylMCIiIiKmUhgRERERUymMiIiIiKkURkRERMRUCiMiIiJiKoURERERMZXCiIiIiJhKYURERERMpTAiIiIiplIYEREREVMpjIiIiIipFEZERETEVAojIiIiYiqFERERETGVwoiIiIiYSmFERERETKUwIiIiIqZSGBERERFTKYyIiIiIqRRGRERExFQKIyIiImIqhRERERExlcKIiIiImEphREREREylMCIiIiKmUhgRERERUymMiIiIiKkURkRERMRUCiMiIiJiKoURERERMZXCiIiIiJhKYURERERMpTAiIiIiplIYEREREVMpjIiIiIipFEZERETEVAojIiIiYiqFERERETGVwoiIiIiYSmFERERETKUwIiIiIqZSGBERERFTKYyIiIiIqRRGRERExFQKIyIiImIqhRERERExlcKIiIiImEphREREREylMCIiIiKmUhgRERERUymMiIiIiKkURkRERMRUCiMiIiJiKoURERERMZXCiIiIiJhKYURERERMpTAiIiIiplIYEREREVMpjIiIiIipziqMvPbaa8THx+Pr68uAAQNYvXr1Gdt/+umndOrUCV9fX7p3787XX399VsWKiIjIhafOYeSTTz5h6tSpPP7446xfv57ExESGDx9OTk7OaduvWLGCm266idtuu40NGzYwZswYxowZw9atW8+5eBEREWn8LIZhGHXZYcCAAfTr149XX30VAKfTSWxsLHfffTcPP/zwKe1vuOEGiouLmTdvnnvbwIED6dmzJ2+++eZpj1FeXk55ebn75/z8fOLi4sjIyCAoKKgu5YqIiIhJCgoKiI2NJS8vj+Dg4BrbedXlQysqKli3bh3Tpk1zb7NarQwbNozk5OTT7pOcnMzUqVOrbRs+fDhz586t8TjTp0/nySefPGV7bGxsXcoVERGR80BhYWH9hZEjR47gcDiIjIystj0yMpKdO3eedp+srKzTts/KyqrxONOmTasWYJxOJ7m5uYSHh2OxWOpS8hmdSGwacfE89XXDUV83LPV3w1FfN5z66mvDMCgsLCQmJuaM7eoURhqK3W7HbrdX2xYSEuKx4wUFBel/2A1Efd1w1NcNS/3dcNTXDac++vpMIyIn1GkCa/PmzbHZbGRnZ1fbnp2dTVRU1Gn3iYqKqlN7ERERaVrqFEZ8fHzo06cPixYtcm9zOp0sWrSIpKSk0+6TlJRUrT3AwoULa2wvIiIiTUudL9NMnTqViRMn0rdvX/r378/LL79McXExkyZNAmDChAm0bNmS6dOnA3Dvvfdy8cUX88ILLzBy5Ehmz57N2rVreeutt+r3TM6C3W7n8ccfP+WSkNQ/9XXDUV83LPV3w1FfN5yG7us639oL8Oqrr/L888+TlZVFz549eeWVVxgwYAAAl1xyCfHx8cycOdPd/tNPP+WRRx5h//79tG/fnr///e9cffXV9XYSIiIi0nidVRgRERERqS96No2IiIiYSmFERERETKUwIiIiIqZSGBERERFTNekw8tprrxEfH4+vry8DBgxg9erVZpfU6E2fPp1+/foRGBhIREQEY8aMISUlpVqbsrIyJk+eTHh4OAEBAYwbN+6UhfGkbp577jksFgv33Xefe5v6uX4dPHiQ3//+94SHh+Pn50f37t1Zu3at+33DMHjssceIjo7Gz8+PYcOGsXv3bhMrbpwcDgePPvooCQkJ+Pn50bZtW55++ml+ea+F+vrsLFu2jFGjRhETE4PFYjnlGXG16dfc3FzGjx9PUFAQISEh3HbbbRQVFZ17cUYTNXv2bMPHx8d49913jW3bthl33HGHERISYmRnZ5tdWqM2fPhwY8aMGcbWrVuNjRs3GldffbURFxdnFBUVudvceeedRmxsrLFo0SJj7dq1xsCBA41BgwaZWHXjtnr1aiM+Pt7o0aOHce+997q3q5/rT25urtG6dWvjlltuMVatWmXs27fP+Pbbb409e/a42zz33HNGcHCwMXfuXGPTpk3GtddeayQkJBilpaUmVt74PPPMM0Z4eLgxb948IzU11fj000+NgIAA4x//+Ie7jfr67Hz99dfGX//6V+OLL74wAGPOnDnV3q9Nv1511VVGYmKisXLlSuPHH3802rVrZ9x0003nXFuTDSP9+/c3Jk+e7P7Z4XAYMTExxvTp002s6sKTk5NjAMbSpUsNwzCMvLw8w9vb2/j000/dbXbs2GEARnJyslllNlqFhYVG+/btjYULFxoXX3yxO4yon+vXQw89ZAwZMqTG951OpxEVFWU8//zz7m15eXmG3W43Pv7444Yo8YIxcuRI49Zbb622bezYscb48eMNw1Bf15eTw0ht+nX79u0GYKxZs8bd5ptvvjEsFotx8ODBc6qnSV6mqaioYN26dQwbNsy9zWq1MmzYMJKTk02s7MKTn58PQFhYGADr1q2jsrKyWt936tSJuLg49f1ZmDx5MiNHjqzWn6B+rm9fffUVffv25frrryciIoJevXrx73//2/1+amoqWVlZ1fo7ODiYAQMGqL/raNCgQSxatIhdu3YBsGnTJpYvX86IESMA9bWn1KZfk5OTCQkJoW/fvu42w4YNw2q1smrVqnM6/nn51F5PO3LkCA6Hg8jIyGrbIyMj2blzp0lVXXicTif33XcfgwcPplu3bgBkZWXh4+NzylOYIyMjycrKMqHKxmv27NmsX7+eNWvWnPKe+rl+7du3jzfeeIOpU6fyP//zP6xZs4Z77rkHHx8fJk6c6O7T0/2bov6um4cffpiCggI6deqEzWbD4XDwzDPPMH78eAD1tYfUpl+zsrKIiIio9r6XlxdhYWHn3PdNMoxIw5g8eTJbt25l+fLlZpdywcnIyODee+9l4cKF+Pr6ml3OBc/pdNK3b1+effZZAHr16sXWrVt58803mThxosnVXVj+85//MGvWLD766CO6du3Kxo0bue+++4iJiVFfX8Ca5GWa5s2bY7PZTrmzIDs7m6ioKJOqurBMmTKFefPmsXjxYlq1auXeHhUVRUVFBXl5edXaq+/rZt26deTk5NC7d2+8vLzw8vJi6dKlvPLKK3h5eREZGal+rkfR0dF06dKl2rbOnTuTnp4O4O5T/Zty7h588EEefvhhbrzxRrp3787NN9/M/fff7374qvraM2rTr1FRUeTk5FR7v6qqitzc3HPu+yYZRnx8fOjTpw+LFi1yb3M6nSxatIikpCQTK2v8DMNgypQpzJkzhx9++IGEhIRq7/fp0wdvb+9qfZ+SkkJ6err6vg4uv/xytmzZwsaNG92vvn37Mn78ePff1c/1Z/Dgwafcor5r1y5at24NQEJCAlFRUdX6u6CggFWrVqm/66ikpASrtfpXk81mw+l0AuprT6lNvyYlJZGXl8e6devcbX744QecTqf7Ybln7ZymvzZis2fPNux2uzFz5kxj+/btxh/+8AcjJCTEyMrKMru0Ru2uu+4ygoODjSVLlhiZmZnuV0lJibvNnXfeacTFxRk//PCDsXbtWiMpKclISkoyseoLwy/vpjEM9XN9Wr16teHl5WU888wzxu7du41Zs2YZ/v7+xocffuhu89xzzxkhISHGl19+aWzevNkYPXq0bjc9CxMnTjRatmzpvrX3iy++MJo3b2785S9/cbdRX5+dwsJCY8OGDcaGDRsMwHjxxReNDRs2GGlpaYZh1K5fr7rqKqNXr17GqlWrjOXLlxvt27fXrb3n6p///KcRFxdn+Pj4GP379zdWrlxpdkmNHnDa14wZM9xtSktLjT/96U9GaGio4e/vb1x33XVGZmameUVfIE4OI+rn+vXf//7X6Natm2G3241OnToZb731VrX3nU6n8eijjxqRkZGG3W43Lr/8ciMlJcWkahuvgoIC49577zXi4uIMX19fo02bNsZf//pXo7y83N1GfX12Fi9efNp/nydOnGgYRu369ejRo8ZNN91kBAQEGEFBQcakSZOMwsLCc67NYhi/WNZOREREpIE1yTkjIiIicv5QGBERERFTKYyIiIiIqRRGRERExFQKIyIiImIqhRERERExlcKIiIiImEphREREREylMCIiIiKmUhgRERERUymMiIiIiKn+HxoYgQPXq3MoAAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from pandas import DataFrame\n",
    "DataFrame({'loss': history.history['loss'], 'val_loss': history.history['val_loss']}).plot(ylim=(0, 2.5))\n",
    "print('Final RMSE for the validation set: {:f}'.format(math.sqrt(history.history['val_loss'][-1])))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2a9522f-5519-4422-9602-4b1e9ffc29aa",
   "metadata": {},
   "source": [
    "### Export the trained model"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fe95808-c634-4259-867e-81d5ec52e993",
   "metadata": {},
   "source": [
    "The serving function `serveing_fn` receives raw input data and apply the tranformation before making predictions with the trained model.\n",
    "\n",
    "You can also add some pre/post-processing within the seriving function. In this example, it accepts an unique identifier for each instance and passes through it to the output, that is useful for batch predictions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "f69ec54f-80f1-457a-8dd4-f00004450cfe",
   "metadata": {},
   "outputs": [],
   "source": [
    "def export_serving_model(model, output_dir):\n",
    "\n",
    "    tf_transform_output = tft.TFTransformOutput(TRANSFORM_ARTEFACTS_DIR)\n",
    "    # The layer has to be saved to the model for keras tracking purposes.\n",
    "    model.tft_layer = tf_transform_output.transform_features_layer()\n",
    "\n",
    "    @tf.function\n",
    "    def serveing_fn(uid, is_male, mother_race, mother_age, plurality, gestation_weeks):\n",
    "        features = {\n",
    "            'is_male': is_male, \n",
    "            'mother_race': mother_race, \n",
    "            'mother_age': mother_age, \n",
    "            'plurality': plurality, \n",
    "            'gestation_weeks': gestation_weeks\n",
    "        } \n",
    "        transformed_features = model.tft_layer(features)\n",
    "        outputs = model(transformed_features)\n",
    "        # The prediction results have multiple elements in general.\n",
    "        # But we need only the first element in our case.\n",
    "        outputs = tf.map_fn(lambda item: item[0], outputs)\n",
    "\n",
    "        return {'uid': uid, 'weight': outputs}\n",
    "\n",
    "    concrete_serving_fn = serveing_fn.get_concrete_function(\n",
    "        tf.TensorSpec(shape=[None], dtype=tf.string, name='uid'),\n",
    "        tf.TensorSpec(shape=[None], dtype=tf.string, name='is_male'),\n",
    "        tf.TensorSpec(shape=[None], dtype=tf.string, name='mother_race'),\n",
    "        tf.TensorSpec(shape=[None], dtype=tf.float32, name='mother_age'),\n",
    "        tf.TensorSpec(shape=[None], dtype=tf.float32, name='plurality'),\n",
    "        tf.TensorSpec(shape=[None], dtype=tf.float32, name='gestation_weeks')\n",
    "    )\n",
    "    signatures = {'serving_default': concrete_serving_fn}\n",
    "\n",
    "    model.save(output_dir, save_format='tf', signatures=signatures)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "70cbc8dc-53fb-4ee4-9cf2-ea889437e8c6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:tensorflow_text is not available.\n",
      "INFO:tensorflow:tensorflow_decision_forests is not available.\n",
      "INFO:tensorflow:struct2tensor is not available.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2023-04-19 03:07:36.297528: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Assets written to: gs://etsuji-tft1-babyweight/babyweight_tft/models/export/assets\n"
     ]
    }
   ],
   "source": [
    "EXPORT_DIR = os.path.join(MODELS_DIR, 'export')\n",
    "export_serving_model(trained_model, EXPORT_DIR)\n",
    "os.environ['EXPORT_DIR'] = EXPORT_DIR"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73264505-d99d-4ec4-90e5-5d01f2422dd9",
   "metadata": {},
   "source": [
    "### Explore the exported model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "52cb23d9-8cf2-42ab-8187-5dff62b1a449",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "gs://etsuji-tft1-babyweight/babyweight_tft/models/export/:\n",
      "         0  2023-04-19T03:07:39Z  gs://etsuji-tft1-babyweight/babyweight_tft/models/export/\n",
      "     26766  2023-04-19T03:07:45Z  gs://etsuji-tft1-babyweight/babyweight_tft/models/export/keras_metadata.pb\n",
      "    723731  2023-04-19T03:07:45Z  gs://etsuji-tft1-babyweight/babyweight_tft/models/export/saved_model.pb\n",
      "\n",
      "gs://etsuji-tft1-babyweight/babyweight_tft/models/export/assets/:\n",
      "         0  2023-04-19T03:07:42Z  gs://etsuji-tft1-babyweight/babyweight_tft/models/export/assets/\n",
      "        11  2023-04-19T03:07:44Z  gs://etsuji-tft1-babyweight/babyweight_tft/models/export/assets/is_male\n",
      "        11  2023-04-19T03:07:44Z  gs://etsuji-tft1-babyweight/babyweight_tft/models/export/assets/is_multiple\n",
      "        93  2023-04-19T03:07:43Z  gs://etsuji-tft1-babyweight/babyweight_tft/models/export/assets/mother_race\n",
      "\n",
      "gs://etsuji-tft1-babyweight/babyweight_tft/models/export/variables/:\n",
      "         0  2023-04-19T03:07:39Z  gs://etsuji-tft1-babyweight/babyweight_tft/models/export/variables/\n",
      "     43889  2023-04-19T03:07:41Z  gs://etsuji-tft1-babyweight/babyweight_tft/models/export/variables/variables.data-00000-of-00001\n",
      "      1949  2023-04-19T03:07:41Z  gs://etsuji-tft1-babyweight/babyweight_tft/models/export/variables/variables.index\n",
      "TOTAL: 10 objects, 796450 bytes (777.78 KiB)\n",
      "\n",
      "MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:\n",
      "\n",
      "signature_def['__saved_model_init_op']:\n",
      "  The given SavedModel SignatureDef contains the following input(s):\n",
      "  The given SavedModel SignatureDef contains the following output(s):\n",
      "    outputs['__saved_model_init_op'] tensor_info:\n",
      "        dtype: DT_INVALID\n",
      "        shape: unknown_rank\n",
      "        name: NoOp\n",
      "  Method name is: \n",
      "\n",
      "signature_def['serving_default']:\n",
      "  The given SavedModel SignatureDef contains the following input(s):\n",
      "    inputs['gestation_weeks'] tensor_info:\n",
      "        dtype: DT_FLOAT\n",
      "        shape: (-1)\n",
      "        name: serving_default_gestation_weeks:0\n",
      "    inputs['is_male'] tensor_info:\n",
      "        dtype: DT_STRING\n",
      "        shape: (-1)\n",
      "        name: serving_default_is_male:0\n",
      "    inputs['mother_age'] tensor_info:\n",
      "        dtype: DT_FLOAT\n",
      "        shape: (-1)\n",
      "        name: serving_default_mother_age:0\n",
      "    inputs['mother_race'] tensor_info:\n",
      "        dtype: DT_STRING\n",
      "        shape: (-1)\n",
      "        name: serving_default_mother_race:0\n",
      "    inputs['plurality'] tensor_info:\n",
      "        dtype: DT_FLOAT\n",
      "        shape: (-1)\n",
      "        name: serving_default_plurality:0\n",
      "    inputs['uid'] tensor_info:\n",
      "        dtype: DT_STRING\n",
      "        shape: (-1)\n",
      "        name: serving_default_uid:0\n",
      "  The given SavedModel SignatureDef contains the following output(s):\n",
      "    outputs['uid'] tensor_info:\n",
      "        dtype: DT_STRING\n",
      "        shape: (-1)\n",
      "        name: StatefulPartitionedCall_6:0\n",
      "    outputs['weight'] tensor_info:\n",
      "        dtype: DT_FLOAT\n",
      "        shape: (-1)\n",
      "        name: StatefulPartitionedCall_6:1\n",
      "  Method name is: tensorflow/serving/predict\n",
      "\n",
      "Defined Functions:\n",
      "  Function Name: '__call__'\n",
      "    Option #1\n",
      "      Callable with:\n",
      "        Argument #1\n",
      "          DType: dict\n",
      "          Value: {'mother_race_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/mother_race_index'), 'mother_age_log': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/mother_age_log'), 'mother_age_normalized': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/mother_age_normalized'), 'gestation_weeks_scaled': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/gestation_weeks_scaled'), 'is_multiple_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/is_multiple_index'), 'is_male_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/is_male_index'), 'mother_age_bucketized': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/mother_age_bucketized')}\n",
      "        Argument #2\n",
      "          DType: bool\n",
      "          Value: False\n",
      "        Argument #3\n",
      "          DType: NoneType\n",
      "          Value: None\n",
      "    Option #2\n",
      "      Callable with:\n",
      "        Argument #1\n",
      "          DType: dict\n",
      "          Value: {'gestation_weeks_scaled': TensorSpec(shape=(None,), dtype=tf.float32, name='gestation_weeks_scaled'), 'mother_age_log': TensorSpec(shape=(None,), dtype=tf.float32, name='mother_age_log'), 'mother_age_bucketized': TensorSpec(shape=(None,), dtype=tf.int64, name='mother_age_bucketized'), 'is_male_index': TensorSpec(shape=(None,), dtype=tf.int64, name='is_male_index'), 'mother_age_normalized': TensorSpec(shape=(None,), dtype=tf.float32, name='mother_age_normalized'), 'mother_race_index': TensorSpec(shape=(None,), dtype=tf.int64, name='mother_race_index'), 'is_multiple_index': TensorSpec(shape=(None,), dtype=tf.int64, name='is_multiple_index')}\n",
      "        Argument #2\n",
      "          DType: bool\n",
      "          Value: False\n",
      "        Argument #3\n",
      "          DType: NoneType\n",
      "          Value: None\n",
      "    Option #3\n",
      "      Callable with:\n",
      "        Argument #1\n",
      "          DType: dict\n",
      "          Value: {'gestation_weeks_scaled': TensorSpec(shape=(None,), dtype=tf.float32, name='gestation_weeks_scaled'), 'is_multiple_index': TensorSpec(shape=(None,), dtype=tf.int64, name='is_multiple_index'), 'mother_age_bucketized': TensorSpec(shape=(None,), dtype=tf.int64, name='mother_age_bucketized'), 'is_male_index': TensorSpec(shape=(None,), dtype=tf.int64, name='is_male_index'), 'mother_race_index': TensorSpec(shape=(None,), dtype=tf.int64, name='mother_race_index'), 'mother_age_log': TensorSpec(shape=(None,), dtype=tf.float32, name='mother_age_log'), 'mother_age_normalized': TensorSpec(shape=(None,), dtype=tf.float32, name='mother_age_normalized')}\n",
      "        Argument #2\n",
      "          DType: bool\n",
      "          Value: True\n",
      "        Argument #3\n",
      "          DType: NoneType\n",
      "          Value: None\n",
      "    Option #4\n",
      "      Callable with:\n",
      "        Argument #1\n",
      "          DType: dict\n",
      "          Value: {'mother_age_bucketized': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/mother_age_bucketized'), 'is_male_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/is_male_index'), 'mother_race_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/mother_race_index'), 'mother_age_log': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/mother_age_log'), 'gestation_weeks_scaled': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/gestation_weeks_scaled'), 'is_multiple_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/is_multiple_index'), 'mother_age_normalized': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/mother_age_normalized')}\n",
      "        Argument #2\n",
      "          DType: bool\n",
      "          Value: True\n",
      "        Argument #3\n",
      "          DType: NoneType\n",
      "          Value: None\n",
      "\n",
      "  Function Name: '_default_save_signature'\n",
      "    Option #1\n",
      "      Callable with:\n",
      "        Argument #1\n",
      "          DType: dict\n",
      "          Value: {'mother_age_bucketized': TensorSpec(shape=(None,), dtype=tf.int64, name='mother_age_bucketized'), 'is_multiple_index': TensorSpec(shape=(None,), dtype=tf.int64, name='is_multiple_index'), 'mother_age_normalized': TensorSpec(shape=(None,), dtype=tf.float32, name='mother_age_normalized'), 'is_male_index': TensorSpec(shape=(None,), dtype=tf.int64, name='is_male_index'), 'mother_race_index': TensorSpec(shape=(None,), dtype=tf.int64, name='mother_race_index'), 'mother_age_log': TensorSpec(shape=(None,), dtype=tf.float32, name='mother_age_log'), 'gestation_weeks_scaled': TensorSpec(shape=(None,), dtype=tf.float32, name='gestation_weeks_scaled')}\n",
      "\n",
      "  Function Name: 'call_and_return_all_conditional_losses'\n",
      "    Option #1\n",
      "      Callable with:\n",
      "        Argument #1\n",
      "          DType: dict\n",
      "          Value: {'mother_age_bucketized': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/mother_age_bucketized'), 'mother_age_normalized': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/mother_age_normalized'), 'mother_race_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/mother_race_index'), 'is_male_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/is_male_index'), 'mother_age_log': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/mother_age_log'), 'is_multiple_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/is_multiple_index'), 'gestation_weeks_scaled': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/gestation_weeks_scaled')}\n",
      "        Argument #2\n",
      "          DType: bool\n",
      "          Value: False\n",
      "        Argument #3\n",
      "          DType: NoneType\n",
      "          Value: None\n",
      "    Option #2\n",
      "      Callable with:\n",
      "        Argument #1\n",
      "          DType: dict\n",
      "          Value: {'gestation_weeks_scaled': TensorSpec(shape=(None,), dtype=tf.float32, name='gestation_weeks_scaled'), 'mother_age_log': TensorSpec(shape=(None,), dtype=tf.float32, name='mother_age_log'), 'mother_age_normalized': TensorSpec(shape=(None,), dtype=tf.float32, name='mother_age_normalized'), 'is_multiple_index': TensorSpec(shape=(None,), dtype=tf.int64, name='is_multiple_index'), 'mother_age_bucketized': TensorSpec(shape=(None,), dtype=tf.int64, name='mother_age_bucketized'), 'mother_race_index': TensorSpec(shape=(None,), dtype=tf.int64, name='mother_race_index'), 'is_male_index': TensorSpec(shape=(None,), dtype=tf.int64, name='is_male_index')}\n",
      "        Argument #2\n",
      "          DType: bool\n",
      "          Value: False\n",
      "        Argument #3\n",
      "          DType: NoneType\n",
      "          Value: None\n",
      "    Option #3\n",
      "      Callable with:\n",
      "        Argument #1\n",
      "          DType: dict\n",
      "          Value: {'mother_age_log': TensorSpec(shape=(None,), dtype=tf.float32, name='mother_age_log'), 'mother_age_normalized': TensorSpec(shape=(None,), dtype=tf.float32, name='mother_age_normalized'), 'is_multiple_index': TensorSpec(shape=(None,), dtype=tf.int64, name='is_multiple_index'), 'gestation_weeks_scaled': TensorSpec(shape=(None,), dtype=tf.float32, name='gestation_weeks_scaled'), 'mother_race_index': TensorSpec(shape=(None,), dtype=tf.int64, name='mother_race_index'), 'is_male_index': TensorSpec(shape=(None,), dtype=tf.int64, name='is_male_index'), 'mother_age_bucketized': TensorSpec(shape=(None,), dtype=tf.int64, name='mother_age_bucketized')}\n",
      "        Argument #2\n",
      "          DType: bool\n",
      "          Value: True\n",
      "        Argument #3\n",
      "          DType: NoneType\n",
      "          Value: None\n",
      "    Option #4\n",
      "      Callable with:\n",
      "        Argument #1\n",
      "          DType: dict\n",
      "          Value: {'mother_age_log': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/mother_age_log'), 'mother_age_bucketized': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/mother_age_bucketized'), 'is_male_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/is_male_index'), 'is_multiple_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/is_multiple_index'), 'gestation_weeks_scaled': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/gestation_weeks_scaled'), 'mother_age_normalized': TensorSpec(shape=(None,), dtype=tf.float32, name='inputs/mother_age_normalized'), 'mother_race_index': TensorSpec(shape=(None,), dtype=tf.int64, name='inputs/mother_race_index')}\n",
      "        Argument #2\n",
      "          DType: bool\n",
      "          Value: True\n",
      "        Argument #3\n",
      "          DType: NoneType\n",
      "          Value: None\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2023-04-19 03:07:54.997944: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/cuda/lib64:/usr/local/nccl2/lib:/usr/local/cuda/extras/CUPTI/lib64\n",
      "2023-04-19 03:07:54.998000: W tensorflow/stream_executor/cuda/cuda_driver.cc:269] failed call to cuInit: UNKNOWN ERROR (303)\n",
      "2023-04-19 03:07:54.998030: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (tensorflow-2-8-20230419-113852): /proc/driver/nvidia/version does not exist\n"
     ]
    }
   ],
   "source": [
    "%%bash\n",
    "gcloud storage ls --long --recursive $EXPORT_DIR\n",    "saved_model_cli show --all --dir=$EXPORT_DIR"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f98b507-2101-406e-9f23-d25865341a85",
   "metadata": {},
   "source": [
    "## 4. Use the exported model"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a642c580-fd0d-43ac-85e7-241505ca2203",
   "metadata": {},
   "source": [
    "### Load the exported model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "4d6661d7-a9be-459e-8a9f-08f2a7aa0669",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Inconsistent references when loading the checkpoint into this object graph. Either the Trackable object references in the Python program have changed in an incompatible way, or the checkpoint was generated in an incompatible program.\n",
      "\n",
      "Two checkpoint references resolved to different objects (<keras.saving.saved_model.load.TensorFlowTransform>TransformFeaturesLayer object at 0x7fd66101e5d0> and <keras.engine.input_layer.InputLayer object at 0x7fd660e57050>).\n"
     ]
    }
   ],
   "source": [
    "#tf.keras.backend.clear_session()\n",
    "model = tf.keras.models.load_model(EXPORT_DIR)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad4fa8ee-7ffd-4013-805a-01b7b83d3e31",
   "metadata": {},
   "source": [
    "### Define a local prediction function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "432b4d1c-00b5-4d76-970b-08aab452711e",
   "metadata": {},
   "source": [
    "The serving function of the exported model accepts input data with named options. You can use this wrapper to use input data in the standard python dictionary.\n",
    "\n",
    "When you deply the model to Vertex AI using the pre-built container, you can use the Python client library to make online predictions without the wrapper."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "e23303f7-520a-4bca-ae30-42e9a09c46f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "def predict(requests):\n",
    "\n",
    "    uid, is_male, mother_race, mother_age, plurality, gestation_weeks = [], [], [], [], [], []\n",
    "    for instance in requests:\n",
    "        uid.append(instance['uid'])\n",
    "        is_male.append(instance['is_male'])\n",
    "        mother_race.append(instance['mother_race'])\n",
    "        mother_age.append(instance['mother_age'])\n",
    "        plurality.append(instance['plurality'])\n",
    "        gestation_weeks.append(float(instance['gestation_weeks']))\n",
    "\n",
    "    result = model.signatures['serving_default'](\n",
    "        uid = tf.convert_to_tensor(uid),\n",
    "        is_male = tf.convert_to_tensor(is_male),\n",
    "        mother_race = tf.convert_to_tensor(mother_race),\n",
    "        mother_age = tf.convert_to_tensor(mother_age),\n",
    "        plurality = tf.convert_to_tensor(plurality),\n",
    "        gestation_weeks = tf.convert_to_tensor(gestation_weeks))\n",
    "\n",
    "    result = zip(result['uid'].numpy().tolist(), result['weight'].numpy().tolist())\n",
    "    result = [{'uid': output[0].decode('ascii'), 'weight': output[1]} for output in result]\n",
    "    return {'predictions': result}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ada1864e-3836-4d09-93c4-7a64c48a7184",
   "metadata": {},
   "source": [
    "### Make local predictions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "b1b4fc11-c3ee-4e75-8aea-0b145dbfbeb2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'predictions': [{'uid': 'instance1', 'weight': 7.161160945892334},\n",
       "  {'uid': 'instance2', 'weight': 6.278735160827637}]}"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "instance1 = {\n",
    "    'uid': 'instance1',\n",
    "    'is_male': 'True',\n",
    "    'mother_age': 26.0,\n",
    "    'mother_race': 'Asian Indian',\n",
    "    'plurality': 1.0,\n",
    "    'gestation_weeks': 39\n",
    "}\n",
    "\n",
    "instance2 = {\n",
    "    'uid': 'instance2',\n",
    "    'is_male': 'False',\n",
    "    'mother_age': 40.0,\n",
    "    'mother_race': 'Japanese',\n",
    "    'plurality': 2.0,\n",
    "    'gestation_weeks': 37\n",
    "}\n",
    "\n",
    "predict([instance1, instance2])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7aad480-f3b4-41a8-9d0f-59ec945cec3f",
   "metadata": {},
   "source": [
    "### Deploy model to Vertex AI"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "503d3cfd-1583-494a-8679-6467174868f9",
   "metadata": {},
   "source": [
    "Deployment of the model may take minutes. Please hang on."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "a8fdcbfd-7fe1-4fb9-a8a3-6dc4057a56ca",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using endpoint [https://us-central1-aiplatform.googleapis.com/]\n",
      "Waiting for operation [5478426430398267392]...\n",
      ".......................................done.\n",
      "Using endpoint [https://us-central1-aiplatform.googleapis.com/]\n",
      "Using endpoint [https://us-central1-aiplatform.googleapis.com/]\n",
      "Waiting for operation [7810165137469341696]...\n",
      "......done.\n",
      "Created Vertex AI endpoint: projects/837723027153/locations/us-central1/endpoints/8255287232970096640.\n",
      "Using endpoint [https://us-central1-aiplatform.googleapis.com/]\n",
      "Using endpoint [https://us-central1-aiplatform.googleapis.com/]\n",
      "Waiting for operation [1717920741543903232]...\n",
      "...........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................done.\n",
      "Deployed a model to the endpoint 8255287232970096640. Id of the deployed model: 8044768139646337024.\n"
     ]
    }
   ],
   "source": [
    "%%bash\n",
    "ts=$(date +%y%m%d-%H%M%S)\n",
    "MODEL_NAME=\"babyweight-$ts\"\n",
    "ENDPOINT_NAME=\"babyweight-endpoint-$ts\"\n",
    "\n",
    "gcloud ai models upload --region=$REGION --display-name=$MODEL_NAME \\\n",
    "  --container-image-uri=us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-7:latest \\\n",
    "  --artifact-uri=$EXPORT_DIR\n",
    "MODEL_ID=$(gcloud ai models list --region=$REGION \\\n",
    "  --filter=display_name=$MODEL_NAME --format \"value(MODEL_ID)\")\n",
    "\n",
    "gcloud ai endpoints create --region=$REGION --display-name=$ENDPOINT_NAME\n",
    "ENDPOINT_ID=$(gcloud ai endpoints list --region=$REGION \\\n",
    "  --filter=display_name=$ENDPOINT_NAME --format \"value(ENDPOINT_ID)\")\n",
    "\n",
    "gcloud ai endpoints deploy-model $ENDPOINT_ID \\\n",
    "  --region=$REGION --model=$MODEL_ID --display-name=$MODEL_NAME \\\n",
    "  --machine-type=n1-standard-2 \\\n",
    "  --min-replica-count=1 --max-replica-count=3 --traffic-split=0=100"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c331827c-63dd-4b46-9f4c-0775c3a00f21",
   "metadata": {},
   "source": [
    "### Make online preditions with Python client library"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "8f7a5ddc-9b69-4c26-870c-772156cf23d5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'uid': 'instance1', 'weight': 7.16116095},\n",
       " {'weight': 6.27873516, 'uid': 'instance2'}]"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from google.cloud import aiplatform\n",
    "\n",
    "# Use the latest endpoint that starts with 'babyweight-endpoint'\n",
    "endpoints = aiplatform.Endpoint.list(\n",
    "    project=PROJECT, location=REGION, order_by='create_time desc')\n",
    "for item in endpoints:\n",
    "    if item.display_name.startswith('babyweight-endpoint'):\n",
    "        endpoint = item\n",
    "        break\n",
    "\n",
    "endpoint.predict(instances=[instance1, instance2]).predictions"
   ]
  }
 ],
 "metadata": {
  "environment": {
   "kernel": "python3",
   "name": "tf2-gpu.2-8.m107",
   "type": "gcloud",
   "uri": "gcr.io/deeplearning-platform-release/tf2-gpu.2-8:m107"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
